<!DOCTYPE html>
<html lang="en">
    <head hexo-theme='https://github.com/volantis-x/hexo-theme-volantis/tree/5.0.0.alpha.2'>
  <meta name="generator" content="Hexo 5.4.0">
  <meta charset="utf-8">
  <!-- SEO相关 -->
  
  <link rel="canonical" href="https://www.bcoder.top/2021/09/05/javadoop系列：一行一行源码分析清楚-abstractqueuedsynchronizer-二/"/>
  <!-- 渲染优化 -->
  <meta http-equiv='x-dns-prefetch-control' content='on' />
  <link rel='dns-prefetch' href='https://cdn.jsdelivr.net'>
  <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
  <meta name="renderer" content="webkit">
  <meta name="force-rendering" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="HandheldFriendly" content="True" >
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">
  <meta content="进击的Coder" name="application-name">
  <meta content="进击的Coder" name="apple-mobile-web-app-title">
  <meta content="black-translucent" name="apple-mobile-web-app-status-bar-style">
  <meta content="telephone=no" name="format-detection">
  <link rel="preload" href="/css/style.css?time=1630853129176" as="style">
  <link rel="preload" href="https://cdn.jsdelivr.net/gh/volantis-x/cdn-fonts/VarelaRound/VarelaRound-Regular.ttf" as="font" type="font/ttf" crossorigin="anonymous">
<link rel="preload" href="https://cdn.jsdelivr.net/gh/volantis-x/cdn-fonts/UbuntuMono/UbuntuMono-Regular.ttf" as="font" type="font/ttf" crossorigin="anonymous">

  <!-- 页面元数据 -->
  <title>Javadoop系列：一行一行源码分析清楚 AbstractQueuedSynchronizer (二) - 进击的Coder</title>
  <meta name="keywords" content="AQS,null">
  <meta name="description" content="文章比较长，信息量比较大，建议在 pc 上阅读。文章标题是为了呼应前文，其实可以单独成文的，主要是希望读者看文章能系统看。 本文关注以下几点内容：  深入理解 ReentrantLock 公平锁和非公平锁的区别 深入分析 AbstractQueuedSynchronizer 中的 ConditionObject 深入理解 Java 线程中断和 InterruptedException 异常  基本">
<meta property="og:type" content="article">
<meta property="og:title" content="Javadoop系列：一行一行源码分析清楚 AbstractQueuedSynchronizer (二)">
<meta property="og:url" content="https://www.bcoder.top/2021/09/05/Javadoop%E7%B3%BB%E5%88%97%EF%BC%9A%E4%B8%80%E8%A1%8C%E4%B8%80%E8%A1%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E6%B8%85%E6%A5%9A-AbstractQueuedSynchronizer-%E4%BA%8C/">
<meta property="og:site_name" content="进击的Coder">
<meta property="og:description" content="文章比较长，信息量比较大，建议在 pc 上阅读。文章标题是为了呼应前文，其实可以单独成文的，主要是希望读者看文章能系统看。 本文关注以下几点内容：  深入理解 ReentrantLock 公平锁和非公平锁的区别 深入分析 AbstractQueuedSynchronizer 中的 ConditionObject 深入理解 Java 线程中断和 InterruptedException 异常  基本">
<meta property="og:locale" content="en_US">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/blog/favicon/android-chrome-192x192.png">
<meta property="article:published_time" content="2021-09-05T07:17:50.000Z">
<meta property="article:modified_time" content="2021-09-05T07:18:05.267Z">
<meta property="article:author" content="zlnnjit">
<meta property="article:tag" content="AQS">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/blog/favicon/android-chrome-192x192.png">
  <!-- feed -->
  <!-- import meta -->
  <!-- link -->
    <link rel="shortcut icon" type='image/x-icon' href="https://cdn.jsdelivr.net/gh/cdn-x/placeholder@1.0.1/avatar/round/3442075.svg">
  <!-- import link -->
  <style>
    /* 首屏样式 */
    #safearea {
  display: none;
}
/* 
  * Workaround for Chrome bug, part 1
  * Chunk rendering for all but the first article.
  * /layout/_partial/scripts/content-visibility-scroll-fix.ejs
*/
.post-story + .post-story,
article + article,
.post-wrapper + .post-wrapper {
  content-visibility: auto;
  contain-intrinsic-size: 10px 500px;
}
:root {
  --color-site-body: #f4f4f4;
  --color-site-bg: #f4f4f4;
  --color-site-inner: #fff;
  --color-site-footer: #666;
  --color-card: #fff;
  --color-text: #444;
  --color-block: #f6f6f6;
  --color-inlinecode: #c74f00;
  --color-codeblock: #fff7ea;
  --color-h1: #3a3a3a;
  --color-h2: #3a3a3a;
  --color-h3: #333;
  --color-h4: #444;
  --color-h5: #555;
  --color-h6: #666;
  --color-p: #444;
  --color-list: #666;
  --color-list-hl: #36ac91;
  --color-meta: #888;
  --color-read-bkg: #e0d8c8;
  --color-read-post: #f8f1e2;
}
* {
  box-sizing: border-box;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  outline: none;
  margin: 0;
  padding: 0;
}
html {
  color: var(--color-text);
  width: 100%;
  height: 100%;
  font-family: UbuntuMono, "Varela Round", "PingFang SC", "Microsoft YaHei", Helvetica, Arial, Menlo, Monaco, monospace, sans-serif;
  font-size: 20px;
}
html >::-webkit-scrollbar {
  height: 4px;
  width: 4px;
}
html >::-webkit-scrollbar-track-piece {
  background: transparent;
}
html >::-webkit-scrollbar-thumb {
  background: #44d7b6;
  cursor: pointer;
  border-radius: 2px;
  -webkit-border-radius: 2px;
}
html >::-webkit-scrollbar-thumb:hover {
  background: #ff5722;
}
body {
  background-color: var(--color-site-body);
  text-rendering: optimizelegibility;
  -webkit-tap-highlight-color: rgba(0,0,0,0);
  line-height: 1.6;
  -webkit-text-size-adjust: 100%;
  -ms-text-size-adjust: 100%;
}
body.modal-active {
  overflow: hidden;
}
@media screen and (max-width: 680px) {
  body.modal-active {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
  }
}
a {
  color: #1b79c4;
  cursor: pointer;
  text-decoration: none;
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
a:hover {
  color: #ff5722;
}
a:active,
a:hover {
  outline: 0;
}
ul,
ol {
  padding-left: 0;
}
ul li,
ol li {
  list-style: none;
}
header {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
}
img {
  border: 0;
  background: none;
  max-width: 100%;
}
svg:not(:root) {
  overflow: hidden;
}
hr {
  -moz-box-sizing: content-box;
  box-sizing: content-box;
  -webkit-box-sizing: content-box;
  -moz-box-sizing: content-box;
  height: 0;
  border: 0;
  border-radius: 1px;
  -webkit-border-radius: 1px;
  border-bottom: 1px solid rgba(68,68,68,0.1);
}
button,
input {
  color: inherit;
  font: inherit;
  margin: 0;
}
button {
  overflow: visible;
  text-transform: none;
  -webkit-appearance: button;
  cursor: pointer;
}
@supports (backdrop-filter: blur(20px)) {
  .blur {
    background: rgba(255,255,255,0.9) !important;
    backdrop-filter: saturate(200%) blur(20px);
  }
}
.shadow {
  box-shadow: 0 1px 2px 0px rgba(0,0,0,0.1);
  -webkit-box-shadow: 0 1px 2px 0px rgba(0,0,0,0.1);
}
.shadow.floatable {
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
.shadow.floatable:hover {
  box-shadow: 0 2px 4px 0px rgba(0,0,0,0.1), 0 4px 8px 0px rgba(0,0,0,0.1), 0 8px 16px 0px rgba(0,0,0,0.1);
  -webkit-box-shadow: 0 2px 4px 0px rgba(0,0,0,0.1), 0 4px 8px 0px rgba(0,0,0,0.1), 0 8px 16px 0px rgba(0,0,0,0.1);
}
#l_cover {
  min-height: 64px;
}
.cover-wrapper {
  top: 0;
  left: 0;
  max-width: 100%;
  height: 100vh;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  flex-wrap: nowrap;
  -webkit-flex-wrap: nowrap;
  -khtml-flex-wrap: nowrap;
  -moz-flex-wrap: nowrap;
  -o-flex-wrap: nowrap;
  -ms-flex-wrap: nowrap;
  -webkit-box-direction: normal;
  -moz-box-direction: normal;
  -webkit-box-orient: vertical;
  -moz-box-orient: vertical;
  -webkit-flex-direction: column;
  -ms-flex-direction: column;
  flex-direction: column;
  align-items: center;
  align-self: center;
  align-content: center;
  color: var(--color-site-inner);
  padding: 0 16px;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  position: relative;
  overflow: hidden;
  margin-bottom: -100px;
}
.cover-wrapper .cover-bg {
  position: absolute;
  width: 100%;
  height: 100%;
  background-position: center;
  background-size: cover;
  -webkit-background-size: cover;
  -moz-background-size: cover;
}
.cover-wrapper .cover-bg.lazyload:not(.loaded) {
  opacity: 0;
  -webkit-opacity: 0;
  -moz-opacity: 0;
}
.cover-wrapper .cover-bg.lazyload.loaded {
  animation-delay: 0s;
  animation-duration: 0.5s;
  animation-fill-mode: forwards;
  animation-timing-function: ease-out;
  animation-name: fadeIn;
}
@-moz-keyframes fadeIn {
  0% {
    opacity: 0;
    -webkit-opacity: 0;
    -moz-opacity: 0;
    filter: blur(12px);
    transform: scale(1.02);
    -webkit-transform: scale(1.02);
    -khtml-transform: scale(1.02);
    -moz-transform: scale(1.02);
    -o-transform: scale(1.02);
    -ms-transform: scale(1.02);
  }
  100% {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@-webkit-keyframes fadeIn {
  0% {
    opacity: 0;
    -webkit-opacity: 0;
    -moz-opacity: 0;
    filter: blur(12px);
    transform: scale(1.02);
    -webkit-transform: scale(1.02);
    -khtml-transform: scale(1.02);
    -moz-transform: scale(1.02);
    -o-transform: scale(1.02);
    -ms-transform: scale(1.02);
  }
  100% {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@-o-keyframes fadeIn {
  0% {
    opacity: 0;
    -webkit-opacity: 0;
    -moz-opacity: 0;
    filter: blur(12px);
    transform: scale(1.02);
    -webkit-transform: scale(1.02);
    -khtml-transform: scale(1.02);
    -moz-transform: scale(1.02);
    -o-transform: scale(1.02);
    -ms-transform: scale(1.02);
  }
  100% {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@keyframes fadeIn {
  0% {
    opacity: 0;
    -webkit-opacity: 0;
    -moz-opacity: 0;
    filter: blur(12px);
    transform: scale(1.02);
    -webkit-transform: scale(1.02);
    -khtml-transform: scale(1.02);
    -moz-transform: scale(1.02);
    -o-transform: scale(1.02);
    -ms-transform: scale(1.02);
  }
  100% {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
.cover-wrapper .cover-body {
  z-index: 1;
  position: relative;
  width: 100%;
  height: 100%;
}
.cover-wrapper#full {
  height: calc(100vh + 100px);
  padding-bottom: 100px;
}
.cover-wrapper#half {
  max-height: 640px;
  min-height: 400px;
  height: calc(36vh - 64px + 200px);
}
.cover-wrapper #scroll-down {
  width: 100%;
  height: 64px;
  position: absolute;
  bottom: 100px;
  text-align: center;
  cursor: pointer;
}
.cover-wrapper #scroll-down .scroll-down-effects {
  color: #fff;
  font-size: 24px;
  line-height: 64px;
  position: absolute;
  width: 24px;
  left: calc(50% - 12px);
  text-shadow: 0 1px 2px rgba(0,0,0,0.1);
  animation: scroll-down-effect 1.5s infinite;
  -webkit-animation: scroll-down-effect 1.5s infinite;
  -khtml-animation: scroll-down-effect 1.5s infinite;
  -moz-animation: scroll-down-effect 1.5s infinite;
  -o-animation: scroll-down-effect 1.5s infinite;
  -ms-animation: scroll-down-effect 1.5s infinite;
}
@-moz-keyframes scroll-down-effect {
  0% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
  50% {
    top: -16px;
    opacity: 0.4;
    -webkit-opacity: 0.4;
    -moz-opacity: 0.4;
  }
  100% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@-webkit-keyframes scroll-down-effect {
  0% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
  50% {
    top: -16px;
    opacity: 0.4;
    -webkit-opacity: 0.4;
    -moz-opacity: 0.4;
  }
  100% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@-o-keyframes scroll-down-effect {
  0% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
  50% {
    top: -16px;
    opacity: 0.4;
    -webkit-opacity: 0.4;
    -moz-opacity: 0.4;
  }
  100% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@keyframes scroll-down-effect {
  0% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
  50% {
    top: -16px;
    opacity: 0.4;
    -webkit-opacity: 0.4;
    -moz-opacity: 0.4;
  }
  100% {
    top: 0;
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
.cover-wrapper .cover-body {
  margin-top: 64px;
  margin-bottom: 100px;
}
.cover-wrapper .cover-body,
.cover-wrapper .cover-body .top,
.cover-wrapper .cover-body .bottom {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  -webkit-box-direction: normal;
  -moz-box-direction: normal;
  -webkit-box-orient: vertical;
  -moz-box-orient: vertical;
  -webkit-flex-direction: column;
  -ms-flex-direction: column;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  -webkit-justify-content: center;
  -khtml-justify-content: center;
  -moz-justify-content: center;
  -o-justify-content: center;
  -ms-justify-content: center;
  max-width: 100%;
}
.cover-wrapper .cover-body .bottom {
  margin-top: 32px;
}
.cover-wrapper .cover-body .title {
  font-family: "Varela Round", "PingFang SC", "Microsoft YaHei", Helvetica, Arial, Helvetica, monospace;
  font-size: 3.125rem;
  line-height: 1.2;
  text-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.cover-wrapper .cover-body .subtitle {
  font-size: 25px;
}
.cover-wrapper .cover-body .logo {
  max-height: 120px;
  max-width: calc(100% - 4 * 16px);
}
@media screen and (min-height: 1024px) {
  .cover-wrapper .cover-body .title {
    font-size: 3rem;
  }
  .cover-wrapper .cover-body .subtitle {
    font-size: 1.05rem;
  }
  .cover-wrapper .cover-body .logo {
    max-height: 150px;
  }
}
.cover-wrapper .cover-body .m_search {
  position: relative;
  max-width: calc(100% - 16px);
  width: 320px;
  vertical-align: middle;
}
.cover-wrapper .cover-body .m_search .form {
  position: relative;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  width: 100%;
}
.cover-wrapper .cover-body .m_search .icon,
.cover-wrapper .cover-body .m_search .input {
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
.cover-wrapper .cover-body .m_search .icon {
  position: absolute;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  line-height: 2.5rem;
  width: 32px;
  top: 0;
  left: 5px;
  color: rgba(68,68,68,0.75);
}
.cover-wrapper .cover-body .m_search .input {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  height: 2.5rem;
  width: 100%;
  box-shadow: none;
  -webkit-box-shadow: none;
  box-sizing: border-box;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  font-size: 0.875rem;
  -webkit-appearance: none;
  padding-left: 36px;
  border-radius: 1.4rem;
  -webkit-border-radius: 1.4rem;
  background: rgba(255,255,255,0.6);
  backdrop-filter: blur(10px);
  border: none;
  color: var(--color-text);
}
@media screen and (max-width: 500px) {
  .cover-wrapper .cover-body .m_search .input {
    padding-left: 36px;
  }
}
.cover-wrapper .cover-body .m_search .input:hover {
  background: rgba(255,255,255,0.8);
}
.cover-wrapper .cover-body .m_search .input:focus {
  background: #fff;
}
.cover-wrapper .list-h {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  -webkit-box-direction: normal;
  -moz-box-direction: normal;
  -webkit-box-orient: horizontal;
  -moz-box-orient: horizontal;
  -webkit-flex-direction: row;
  -ms-flex-direction: row;
  flex-direction: row;
  flex-wrap: wrap;
  -webkit-flex-wrap: wrap;
  -khtml-flex-wrap: wrap;
  -moz-flex-wrap: wrap;
  -o-flex-wrap: wrap;
  -ms-flex-wrap: wrap;
  align-items: stretch;
  border-radius: 4px;
  -webkit-border-radius: 4px;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}
.cover-wrapper .list-h a {
  -webkit-box-flex: 1;
  -moz-box-flex: 1;
  -webkit-flex: 1 0;
  -ms-flex: 1 0;
  flex: 1 0;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  font-weight: 600;
}
.cover-wrapper .list-h a img {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  border-radius: 2px;
  -webkit-border-radius: 2px;
  margin: 4px;
  min-width: 40px;
  max-width: 44px;
}
@media screen and (max-width: 768px) {
  .cover-wrapper .list-h a img {
    min-width: 36px;
    max-width: 40px;
  }
}
@media screen and (max-width: 500px) {
  .cover-wrapper .list-h a img {
    margin: 2px 4px;
    min-width: 32px;
    max-width: 36px;
  }
}
@media screen and (max-width: 375px) {
  .cover-wrapper .list-h a img {
    min-width: 28px;
    max-width: 32px;
  }
}
.cover-wrapper {
  max-width: 100%;
}
.cover-wrapper.search .bottom .menu {
  margin-top: 16px;
}
.cover-wrapper.search .bottom .menu .list-h a {
  white-space: nowrap;
  -webkit-box-direction: normal;
  -moz-box-direction: normal;
  -webkit-box-orient: horizontal;
  -moz-box-orient: horizontal;
  -webkit-flex-direction: row;
  -ms-flex-direction: row;
  flex-direction: row;
  align-items: baseline;
  padding: 2px;
  margin: 4px;
  color: var(--color-site-inner);
  opacity: 0.75;
  -webkit-opacity: 0.75;
  -moz-opacity: 0.75;
  text-shadow: 0 1px 2px rgba(0,0,0,0.05);
  border-bottom: 2px solid transparent;
}
.cover-wrapper.search .bottom .menu .list-h a i {
  margin-right: 4px;
}
.cover-wrapper.search .bottom .menu .list-h a p {
  font-size: 0.9375rem;
}
.cover-wrapper.search .bottom .menu .list-h a:hover,
.cover-wrapper.search .bottom .menu .list-h a.active,
.cover-wrapper.search .bottom .menu .list-h a:active {
  opacity: 1;
  -webkit-opacity: 1;
  -moz-opacity: 1;
  border-bottom: 2px solid var(--color-site-inner);
}
.cover-wrapper.dock .menu,
.cover-wrapper.featured .menu,
.cover-wrapper.focus .menu {
  border-radius: 6px;
  -webkit-border-radius: 6px;
}
.cover-wrapper.dock .menu .list-h a,
.cover-wrapper.featured .menu .list-h a,
.cover-wrapper.focus .menu .list-h a {
  -webkit-box-direction: normal;
  -moz-box-direction: normal;
  -webkit-box-orient: vertical;
  -moz-box-orient: vertical;
  -webkit-flex-direction: column;
  -ms-flex-direction: column;
  flex-direction: column;
  align-items: center;
  padding: 12px;
  line-height: 24px;
  border-radius: 4px;
  -webkit-border-radius: 4px;
  border-bottom: none;
  text-align: center;
  align-content: flex-end;
  color: rgba(68,68,68,0.7);
  font-size: 1.5rem;
}
@media screen and (max-width: 500px) {
  .cover-wrapper.dock .menu .list-h a,
  .cover-wrapper.featured .menu .list-h a,
  .cover-wrapper.focus .menu .list-h a {
    padding: 12px 8px;
  }
}
.cover-wrapper.dock .menu .list-h a i,
.cover-wrapper.featured .menu .list-h a i,
.cover-wrapper.focus .menu .list-h a i {
  margin: 8px;
}
.cover-wrapper.dock .menu .list-h a p,
.cover-wrapper.featured .menu .list-h a p,
.cover-wrapper.focus .menu .list-h a p {
  font-size: 0.875rem;
}
.cover-wrapper.dock .menu .list-h a.active,
.cover-wrapper.featured .menu .list-h a.active,
.cover-wrapper.focus .menu .list-h a.active {
  background: var(--color-card);
  backdrop-filter: none;
}
.cover-wrapper.dock .menu .list-h a.active i,
.cover-wrapper.featured .menu .list-h a.active i,
.cover-wrapper.focus .menu .list-h a.active i,
.cover-wrapper.dock .menu .list-h a.active i+p,
.cover-wrapper.featured .menu .list-h a.active i+p,
.cover-wrapper.focus .menu .list-h a.active i+p {
  color: #44d7b6;
}
.cover-wrapper.dock .menu .list-h a.active img+p,
.cover-wrapper.featured .menu .list-h a.active img+p,
.cover-wrapper.focus .menu .list-h a.active img+p {
  color: var(--color-text);
}
.cover-wrapper.dock .menu .list-h a:hover,
.cover-wrapper.featured .menu .list-h a:hover,
.cover-wrapper.focus .menu .list-h a:hover {
  background: var(--color-card);
}
.cover-wrapper.dock .top {
  margin-bottom: 48px;
}
.cover-wrapper.dock .menu {
  background: rgba(255,255,255,0.5);
  position: absolute;
  bottom: 0;
  max-width: 100%;
}
.cover-wrapper.dock .menu .list-h {
  flex-wrap: nowrap;
  -webkit-flex-wrap: nowrap;
  -khtml-flex-wrap: nowrap;
  -moz-flex-wrap: nowrap;
  -o-flex-wrap: nowrap;
  -ms-flex-wrap: nowrap;
  margin: 4px;
}
.cover-wrapper.dock .menu .list-h a+a {
  margin-left: 4px;
}
@media screen and (max-width: 500px) {
  .cover-wrapper.dock .menu .list-h {
    overflow-x: scroll;
  }
  .cover-wrapper.dock .menu .list-h::-webkit-scrollbar {
    height: 0;
    width: 0;
  }
  .cover-wrapper.dock .menu .list-h::-webkit-scrollbar-track-piece {
    background: transparent;
  }
  .cover-wrapper.dock .menu .list-h::-webkit-scrollbar-thumb {
    background: #44d7b6;
    cursor: pointer;
    border-radius: 0;
    -webkit-border-radius: 0;
  }
  .cover-wrapper.dock .menu .list-h::-webkit-scrollbar-thumb:hover {
    background: #ff5722;
  }
}
@supports (backdrop-filter: blur(20px)) {
  .cover-wrapper.dock .menu {
    background: rgba(255,255,255,0.5);
    backdrop-filter: saturate(200%) blur(20px);
  }
}
@media (prefers-color-scheme: dark) {
  :root {
    --color-mode: 'dark';
  }
  :root:not([color-scheme]) {
    --color-site-body: #000;
    --color-read-bkg: #222;
    --color-read-post: #333;
    --color-site-bg: #222;
    --color-site-inner: #eee;
    --color-site-footer: #aaa;
    --color-card: #333;
    --color-text: #eee;
    --color-block: #3a3a3a;
    --color-codeblock: #343a3c;
    --color-inlinecode: #d56d28;
    --color-h1: #eee;
    --color-h2: #eee;
    --color-h3: #ddd;
    --color-h4: #ddd;
    --color-h5: #ddd;
    --color-h6: #ddd;
    --color-p: #bbb;
    --color-list: #aaa;
    --color-list-hl: #69dfc4;
    --color-meta: #888;
    --color-link: #888;
  }
  :root:not([color-scheme]) img {
    filter: brightness(70%) !important;
  }
  :root:not([color-scheme]) #wrapper .title {
    color: var(--color-meta) !important;
  }
  :root:not([color-scheme]) .l_header ul.nav-list-h>li>a,
  :root:not([color-scheme]) ul.list-v >li>a {
    color: var(--color-list) !important;
  }
  :root:not([color-scheme]) .blur {
    background: var(--color-site-bg) !important;
  }
  :root:not([color-scheme]) .nav-main .u-search-input {
    background: var(--color-card) !important;
  }
  :root:not([color-scheme]) #l_main .article .prev-next>a {
    background: var(--color-block) !important;
  }
  :root:not([color-scheme]) #l_main .article .prev-next>a:hover {
    background: var(--color-card) !important;
  }
  :root:not([color-scheme]) .article blockquote {
    background: var(--color-block) !important;
  }
  :root:not([color-scheme]) .article-title a {
    color: var(--color-h1) !important;
  }
  :root:not([color-scheme]) details>summary {
    color: var(--color-p) !important;
    background: var(--color-site-bg) !important;
  }
  :root:not([color-scheme]) details {
    border: 1px solid var(--color-site-bg) !important;
    background: var(--color-site-bg) !important;
  }
  :root:not([color-scheme]) #u-search .modal,
  :root:not([color-scheme]) #u-search .modal-header,
  :root:not([color-scheme]) #u-search .modal-body {
    background: var(--color-card) !important;
  }
  :root:not([color-scheme]) #u-search .modal-body .modal-results .result:hover {
    background: var(--color-block) !important;
  }
}
[color-scheme='dark'] {
  --color-site-body: #000;
  --color-read-bkg: #222;
  --color-read-post: #333;
  --color-site-bg: #222;
  --color-site-inner: #eee;
  --color-site-footer: #aaa;
  --color-card: #333;
  --color-text: #eee;
  --color-block: #3a3a3a;
  --color-codeblock: #343a3c;
  --color-inlinecode: #d56d28;
  --color-h1: #eee;
  --color-h2: #eee;
  --color-h3: #ddd;
  --color-h4: #ddd;
  --color-h5: #ddd;
  --color-h6: #ddd;
  --color-p: #bbb;
  --color-list: #aaa;
  --color-list-hl: #69dfc4;
  --color-meta: #888;
  --color-link: #888;
}
[color-scheme='dark'] img {
  filter: brightness(70%) !important;
}
[color-scheme='dark'] #wrapper .title {
  color: var(--color-meta) !important;
}
[color-scheme='dark'] .l_header ul.nav-list-h>li>a,
[color-scheme='dark'] ul.list-v >li>a {
  color: var(--color-list) !important;
}
[color-scheme='dark'] .blur {
  background: var(--color-site-bg) !important;
}
[color-scheme='dark'] .nav-main .u-search-input {
  background: var(--color-card) !important;
}
[color-scheme='dark'] #l_main .article .prev-next>a {
  background: var(--color-block) !important;
}
[color-scheme='dark'] #l_main .article .prev-next>a:hover {
  background: var(--color-card) !important;
}
[color-scheme='dark'] .article blockquote {
  background: var(--color-block) !important;
}
[color-scheme='dark'] .article-title a {
  color: var(--color-h1) !important;
}
[color-scheme='dark'] details>summary {
  color: var(--color-p) !important;
  background: var(--color-site-bg) !important;
}
[color-scheme='dark'] details {
  border: 1px solid var(--color-site-bg) !important;
  background: var(--color-site-bg) !important;
}
[color-scheme='dark'] #u-search .modal,
[color-scheme='dark'] #u-search .modal-header,
[color-scheme='dark'] #u-search .modal-body {
  background: var(--color-card) !important;
}
[color-scheme='dark'] #u-search .modal-body .modal-results .result:hover {
  background: var(--color-block) !important;
}
@media screen and (max-width: 500px) {
  [color-scheme='dark'] .l_header .m_search {
    background: var(--color-site-bg) !important;
  }
}
@font-face {
  font-family: 'Varela Round';
  src: url("https://cdn.jsdelivr.net/gh/volantis-x/cdn-fonts/UbuntuMono/UbuntuMono-Regular.ttf");
  font-weight: 'normal';
  font-style: 'normal';
  font-display: swap;
}
@font-face {
  font-family: 'Varela Round';
  src: url("https://cdn.jsdelivr.net/gh/volantis-x/cdn-fonts/VarelaRound/VarelaRound-Regular.ttf");
  font-weight: 'normal';
  font-style: 'normal';
  font-display: swap;
}
.l_header {
  position: fixed;
  z-index: 1000;
  top: 0;
  width: 100%;
  height: 64px;
  background: var(--color-card);
  box-shadow: 0 1px 2px 0px rgba(0,0,0,0.1);
  -webkit-box-shadow: 0 1px 2px 0px rgba(0,0,0,0.1);
}
.l_header.auto {
  transition: opacity 0.4s ease;
  -webkit-transition: opacity 0.4s ease;
  -khtml-transition: opacity 0.4s ease;
  -moz-transition: opacity 0.4s ease;
  -o-transition: opacity 0.4s ease;
  -ms-transition: opacity 0.4s ease;
  visibility: hidden;
}
.l_header.auto.show {
  opacity: 1 !important;
  -webkit-opacity: 1 !important;
  -moz-opacity: 1 !important;
  visibility: visible;
}
.l_header .container {
  margin-left: 16px;
  margin-right: 16px;
}
.l_header #wrapper {
  height: 100%;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}
.l_header #wrapper .nav-main,
.l_header #wrapper .nav-sub {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  flex-wrap: nowrap;
  -webkit-flex-wrap: nowrap;
  -khtml-flex-wrap: nowrap;
  -moz-flex-wrap: nowrap;
  -o-flex-wrap: nowrap;
  -ms-flex-wrap: nowrap;
  justify-content: space-between;
  -webkit-justify-content: space-between;
  -khtml-justify-content: space-between;
  -moz-justify-content: space-between;
  -o-justify-content: space-between;
  -ms-justify-content: space-between;
  align-items: center;
}
.l_header #wrapper .nav-main {
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
.l_header #wrapper.sub .nav-main {
  transform: translateY(-64px);
  -webkit-transform: translateY(-64px);
  -khtml-transform: translateY(-64px);
  -moz-transform: translateY(-64px);
  -o-transform: translateY(-64px);
  -ms-transform: translateY(-64px);
}
.l_header #wrapper .nav-sub {
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
  opacity: 0;
  -webkit-opacity: 0;
  -moz-opacity: 0;
  height: 64px;
  width: calc(100% - 2 * 16px);
  position: absolute;
}
.l_header #wrapper .nav-sub ::-webkit-scrollbar {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
}
@media screen and (min-width: 2048px) {
  .l_header #wrapper .nav-sub {
    max-width: 55vw;
    margin: auto;
  }
}
.l_header #wrapper.sub .nav-sub {
  opacity: 1;
  -webkit-opacity: 1;
  -moz-opacity: 1;
}
.l_header #wrapper .title {
  position: relative;
  color: var(--color-text);
  padding-left: 24px;
  max-height: 64px;
}
.l_header #wrapper .nav-main .title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex-shrink: 0;
  line-height: 64px;
  padding: 0 24px;
  font-size: 1.25rem;
  font-family: "Varela Round", "PingFang SC", "Microsoft YaHei", Helvetica, Arial, Helvetica, monospace;
}
.l_header #wrapper .nav-main .title img {
  height: 64px;
}
.l_header .nav-sub {
  max-width: 2000px;
  margin: auto;
}
.l_header .nav-sub .title {
  font-weight: bold;
  font-family: UbuntuMono, "Varela Round", "PingFang SC", "Microsoft YaHei", Helvetica, Arial, Menlo, Monaco, monospace, sans-serif;
  line-height: 1.2;
  max-height: 64px;
  white-space: normal;
  flex-shrink: 1;
}
.l_header .switcher {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
  line-height: 64px;
  align-items: center;
}
.l_header .switcher .s-toc {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
}
@media screen and (max-width: 768px) {
  .l_header .switcher .s-toc {
    display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
    display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
    display: -ms-flexbox /* TWEENER - IE 10 */;
    display: -webkit-flex /* NEW - Chrome */;
    display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
    display: flex;
  }
}
.l_header .switcher >li {
  height: 48px;
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
  margin: 2px;
}
@media screen and (max-width: 500px) {
  .l_header .switcher >li {
    margin: 0 1px;
    height: 48px;
  }
}
.l_header .switcher >li >a {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  justify-content: center;
  -webkit-justify-content: center;
  -khtml-justify-content: center;
  -moz-justify-content: center;
  -o-justify-content: center;
  -ms-justify-content: center;
  align-items: center;
  width: 48px;
  height: 48px;
  padding: 0.85em 1.1em;
  border-radius: 100px;
  -webkit-border-radius: 100px;
  border: none;
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
  color: #44d7b6;
}
.l_header .switcher >li >a:hover {
  border: none;
}
.l_header .switcher >li >a.active,
.l_header .switcher >li >a:active {
  border: none;
  background: var(--color-site-bg);
}
@media screen and (max-width: 500px) {
  .l_header .switcher >li >a {
    width: 36px;
    height: 48px;
  }
}
.l_header .nav-sub .switcher {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
}
.l_header .m_search {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  height: 64px;
  width: 240px;
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
@media screen and (max-width: 1024px) {
  .l_header .m_search {
    width: 44px;
    min-width: 44px;
  }
  .l_header .m_search input::placeholder {
    opacity: 0;
    -webkit-opacity: 0;
    -moz-opacity: 0;
  }
  .l_header .m_search:hover {
    width: 240px;
  }
  .l_header .m_search:hover input::placeholder {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@media screen and (min-width: 500px) {
  .l_header .m_search:hover .input {
    width: 100%;
  }
  .l_header .m_search:hover .input::placeholder {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
@media screen and (max-width: 500px) {
  .l_header .m_search {
    min-width: 0;
  }
  .l_header .m_search input::placeholder {
    opacity: 1;
    -webkit-opacity: 1;
    -moz-opacity: 1;
  }
}
.l_header .m_search .form {
  position: relative;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  width: 100%;
  align-items: center;
}
.l_header .m_search .icon {
  position: absolute;
  width: 36px;
  left: 5px;
  color: var(--color-meta);
}
@media screen and (max-width: 500px) {
  .l_header .m_search .icon {
    display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
    display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
    display: none;
  }
}
.l_header .m_search .input {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  padding-top: 8px;
  padding-bottom: 8px;
  line-height: 1.3;
  width: 100%;
  color: var(--color-text);
  background: #fafafa;
  box-shadow: none;
  -webkit-box-shadow: none;
  box-sizing: border-box;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  padding-left: 40px;
  font-size: 0.875rem;
  border-radius: 8px;
  -webkit-border-radius: 8px;
  border: none;
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
@media screen and (min-width: 500px) {
  .l_header .m_search .input:focus {
    box-shadow: 0 4px 8px 0px rgba(0,0,0,0.1);
    -webkit-box-shadow: 0 4px 8px 0px rgba(0,0,0,0.1);
  }
}
@media screen and (max-width: 500px) {
  .l_header .m_search .input {
    background: var(--color-block);
    padding-left: 8px;
    border: none;
  }
  .l_header .m_search .input:hover,
  .l_header .m_search .input:focus {
    border: none;
  }
}
@media (max-width: 500px) {
  .l_header .m_search {
    left: 0;
    width: 0;
    overflow: hidden;
    position: absolute;
    background: #fff;
    transition: all 0.28s ease;
    -webkit-transition: all 0.28s ease;
    -khtml-transition: all 0.28s ease;
    -moz-transition: all 0.28s ease;
    -o-transition: all 0.28s ease;
    -ms-transition: all 0.28s ease;
  }
  .l_header .m_search .input {
    border-radius: 32px;
    -webkit-border-radius: 32px;
    margin-left: 16px;
    padding-left: 16px;
  }
  .l_header.z_search-open .m_search {
    width: 100%;
  }
  .l_header.z_search-open .m_search .input {
    width: calc(100% - 120px);
  }
}
ul.m-pc >li>a {
  color: inherit;
  border-bottom: 2px solid transparent;
}
ul.m-pc >li>a:active,
ul.m-pc >li>a.active {
  border-bottom: 2px solid #44d7b6;
}
ul.m-pc li:hover >ul.list-v,
ul.list-v li:hover >ul.list-v {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
}
ul.nav-list-h {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: -ms-flexbox /* TWEENER - IE 10 */;
  display: -webkit-flex /* NEW - Chrome */;
  display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
  display: flex;
  align-items: stretch;
}
ul.nav-list-h>li {
  position: relative;
  justify-content: center;
  -webkit-justify-content: center;
  -khtml-justify-content: center;
  -moz-justify-content: center;
  -o-justify-content: center;
  -ms-justify-content: center;
  height: 100%;
  line-height: 2.4;
  border-radius: 4px;
  -webkit-border-radius: 4px;
}
ul.nav-list-h>li >a {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-weight: 600;
}
ul.list-v {
  z-index: 1;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
  position: absolute;
  background: var(--color-card);
  box-shadow: 0 2px 4px 0px rgba(0,0,0,0.08), 0 4px 8px 0px rgba(0,0,0,0.08), 0 8px 16px 0px rgba(0,0,0,0.08);
  -webkit-box-shadow: 0 2px 4px 0px rgba(0,0,0,0.08), 0 4px 8px 0px rgba(0,0,0,0.08), 0 8px 16px 0px rgba(0,0,0,0.08);
  margin-top: -6px;
  border-radius: 4px;
  -webkit-border-radius: 4px;
  padding: 8px 0;
}
ul.list-v.show {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
}
ul.list-v hr {
  margin-top: 8px;
  margin-bottom: 8px;
}
ul.list-v >li {
  white-space: nowrap;
  word-break: keep-all;
}
ul.list-v >li.header {
  font-size: 0.78125rem;
  font-weight: bold;
  line-height: 2em;
  color: var(--color-meta);
  margin: 8px 16px 4px;
}
ul.list-v >li.header i {
  margin-right: 8px;
}
ul.list-v >li ul {
  margin-left: 0;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
  margin-top: -40px;
}
ul.list-v .aplayer-container {
  min-height: 64px;
  padding: 6px 16px;
}
ul.list-v >li>a {
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  color: var(--color-list);
  font-size: 0.875rem;
  font-weight: bold;
  line-height: 36px;
  padding: 0 20px 0 16px;
  text-overflow: ellipsis;
  margin: 0 4px;
  border-radius: 4px;
  -webkit-border-radius: 4px;
}
@media screen and (max-width: 1024px) {
  ul.list-v >li>a {
    line-height: 40px;
  }
}
ul.list-v >li>a >i {
  margin-right: 8px;
}
ul.list-v >li>a:active,
ul.list-v >li>a.active {
  color: var(--color-list-hl);
}
ul.list-v >li>a:hover {
  color: var(--color-list-hl);
  background: var(--color-site-bg);
}
.l_header .menu >ul>li>a {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: block;
  padding: 0 8px;
}
.l_header .menu >ul>li>a >i {
  margin-right: 4px;
}
.l_header ul.nav-list-h>li {
  color: var(--color-list);
  line-height: 64px;
}
.l_header ul.nav-list-h>li >a {
  max-height: 64px;
  overflow: hidden;
  color: inherit;
}
.l_header ul.nav-list-h>li >a:active,
.l_header ul.nav-list-h>li >a.active {
  color: #44d7b6;
}
.l_header ul.nav-list-h>li:hover>a {
  color: var(--color-list-hl);
}
.l_header ul.nav-list-h>li i.music {
  animation: rotate-effect 1.5s linear infinite;
  -webkit-animation: rotate-effect 1.5s linear infinite;
  -khtml-animation: rotate-effect 1.5s linear infinite;
  -moz-animation: rotate-effect 1.5s linear infinite;
  -o-animation: rotate-effect 1.5s linear infinite;
  -ms-animation: rotate-effect 1.5s linear infinite;
}
@-moz-keyframes rotate-effect {
  0% {
    transform: rotate(0);
    -webkit-transform: rotate(0);
    -khtml-transform: rotate(0);
    -moz-transform: rotate(0);
    -o-transform: rotate(0);
    -ms-transform: rotate(0);
  }
  25% {
    transform: rotate(90deg);
    -webkit-transform: rotate(90deg);
    -khtml-transform: rotate(90deg);
    -moz-transform: rotate(90deg);
    -o-transform: rotate(90deg);
    -ms-transform: rotate(90deg);
  }
  50% {
    transform: rotate(180deg);
    -webkit-transform: rotate(180deg);
    -khtml-transform: rotate(180deg);
    -moz-transform: rotate(180deg);
    -o-transform: rotate(180deg);
    -ms-transform: rotate(180deg);
  }
  75% {
    transform: rotate(270deg);
    -webkit-transform: rotate(270deg);
    -khtml-transform: rotate(270deg);
    -moz-transform: rotate(270deg);
    -o-transform: rotate(270deg);
    -ms-transform: rotate(270deg);
  }
  100% {
    transform: rotate(360deg);
    -webkit-transform: rotate(360deg);
    -khtml-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
  }
}
@-webkit-keyframes rotate-effect {
  0% {
    transform: rotate(0);
    -webkit-transform: rotate(0);
    -khtml-transform: rotate(0);
    -moz-transform: rotate(0);
    -o-transform: rotate(0);
    -ms-transform: rotate(0);
  }
  25% {
    transform: rotate(90deg);
    -webkit-transform: rotate(90deg);
    -khtml-transform: rotate(90deg);
    -moz-transform: rotate(90deg);
    -o-transform: rotate(90deg);
    -ms-transform: rotate(90deg);
  }
  50% {
    transform: rotate(180deg);
    -webkit-transform: rotate(180deg);
    -khtml-transform: rotate(180deg);
    -moz-transform: rotate(180deg);
    -o-transform: rotate(180deg);
    -ms-transform: rotate(180deg);
  }
  75% {
    transform: rotate(270deg);
    -webkit-transform: rotate(270deg);
    -khtml-transform: rotate(270deg);
    -moz-transform: rotate(270deg);
    -o-transform: rotate(270deg);
    -ms-transform: rotate(270deg);
  }
  100% {
    transform: rotate(360deg);
    -webkit-transform: rotate(360deg);
    -khtml-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
  }
}
@-o-keyframes rotate-effect {
  0% {
    transform: rotate(0);
    -webkit-transform: rotate(0);
    -khtml-transform: rotate(0);
    -moz-transform: rotate(0);
    -o-transform: rotate(0);
    -ms-transform: rotate(0);
  }
  25% {
    transform: rotate(90deg);
    -webkit-transform: rotate(90deg);
    -khtml-transform: rotate(90deg);
    -moz-transform: rotate(90deg);
    -o-transform: rotate(90deg);
    -ms-transform: rotate(90deg);
  }
  50% {
    transform: rotate(180deg);
    -webkit-transform: rotate(180deg);
    -khtml-transform: rotate(180deg);
    -moz-transform: rotate(180deg);
    -o-transform: rotate(180deg);
    -ms-transform: rotate(180deg);
  }
  75% {
    transform: rotate(270deg);
    -webkit-transform: rotate(270deg);
    -khtml-transform: rotate(270deg);
    -moz-transform: rotate(270deg);
    -o-transform: rotate(270deg);
    -ms-transform: rotate(270deg);
  }
  100% {
    transform: rotate(360deg);
    -webkit-transform: rotate(360deg);
    -khtml-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
  }
}
@keyframes rotate-effect {
  0% {
    transform: rotate(0);
    -webkit-transform: rotate(0);
    -khtml-transform: rotate(0);
    -moz-transform: rotate(0);
    -o-transform: rotate(0);
    -ms-transform: rotate(0);
  }
  25% {
    transform: rotate(90deg);
    -webkit-transform: rotate(90deg);
    -khtml-transform: rotate(90deg);
    -moz-transform: rotate(90deg);
    -o-transform: rotate(90deg);
    -ms-transform: rotate(90deg);
  }
  50% {
    transform: rotate(180deg);
    -webkit-transform: rotate(180deg);
    -khtml-transform: rotate(180deg);
    -moz-transform: rotate(180deg);
    -o-transform: rotate(180deg);
    -ms-transform: rotate(180deg);
  }
  75% {
    transform: rotate(270deg);
    -webkit-transform: rotate(270deg);
    -khtml-transform: rotate(270deg);
    -moz-transform: rotate(270deg);
    -o-transform: rotate(270deg);
    -ms-transform: rotate(270deg);
  }
  100% {
    transform: rotate(360deg);
    -webkit-transform: rotate(360deg);
    -khtml-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
  }
}
.menu-phone li ul.list-v {
  right: calc(100% - 0.5 * 16px);
}
.menu-phone li ul.list-v ul {
  right: calc(100% - 0.5 * 16px);
}
#wrapper {
  max-width: 2000px;
  margin: auto;
}
@media screen and (min-width: 2048px) {
  #wrapper {
    max-width: 55vw;
  }
}
#wrapper .menu {
  -webkit-box-flex: 1;
  -moz-box-flex: 1;
  -webkit-flex: 1 1;
  -ms-flex: 1 1;
  flex: 1 1;
  margin: 0 16px 0 0;
}
#wrapper .menu .list-v ul {
  left: calc(100% - 0.5 * 16px);
}
.menu-phone {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
  margin-top: 16px;
  right: 8px;
  transition: all 0.28s ease;
  -webkit-transition: all 0.28s ease;
  -khtml-transition: all 0.28s ease;
  -moz-transition: all 0.28s ease;
  -o-transition: all 0.28s ease;
  -ms-transition: all 0.28s ease;
}
.menu-phone ul {
  right: calc(100% - 0.5 * 16px);
}
@media screen and (max-width: 500px) {
  .menu-phone {
    display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
    display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
    display: block;
  }
}
.l_header {
  max-width: 65vw;
  left: calc((100% - 65vw) * 0.5);
  border-bottom-left-radius: 8px;
  border-bottom-right-radius: 8px;
}
@media screen and (max-width: 2048px) {
  .l_header {
    max-width: 2032px;
    left: calc((100% - 2032px) * 0.5);
  }
}
@media screen and (max-width: 2032px) {
  .l_header {
    left: 0;
    border-radius: 0;
    -webkit-border-radius: 0;
    max-width: 100%;
  }
}
@media screen and (max-width: 500px) {
  .l_header .container {
    margin-left: 0;
    margin-right: 0;
  }
  .l_header #wrapper .nav-main .title {
    padding-left: 16px;
    padding-right: 16px;
  }
  .l_header #wrapper .nav-sub {
    width: 100%;
  }
  .l_header #wrapper .nav-sub .title {
    overflow-y: scroll;
    margin-top: 2px;
    padding: 8px 16px;
  }
  .l_header #wrapper .switcher {
    display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
    display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
    display: -ms-flexbox /* TWEENER - IE 10 */;
    display: -webkit-flex /* NEW - Chrome */;
    display: flex /* NEW, Spec - Opera 12.1, Firefox 20+ */;
    display: flex;
    margin-right: 8px;
  }
  .l_header .menu {
    display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
    display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
    display: none;
  }
}
@media screen and (max-width: 500px) {
  .list-v li {
    max-width: 270px;
  }
}
#u-search {
  display: -webkit-box /* OLD - iOS 6-, Safari 3.1-6 */;
  display: -moz-box /* OLD - Firefox 19- (buggy but mostly works) */;
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  padding: 60px 20px;
  z-index: 1001;
}
@media screen and (max-width: 680px) {
  #u-search {
    padding: 0px;
  }
}
@media screen and (prefers-color-scheme: dark) and (max-width: 500px) {
  .l_header .m_search {
    background: var(--color-site-bg) !important;
  }
}

  </style>
  <link rel="stylesheet" href="/css/style.css?time=1630853129176" media="print" onload="this.media='all';this.onload=null">
  <noscript><link rel="stylesheet" href="/css/style.css?time=1630853129176"></noscript>
    <script>
      var userColorScheme=localStorage.getItem("color-scheme")
      if(userColorScheme){
        document.documentElement.setAttribute("color-scheme", userColorScheme);
      }
    </script>
  
<script>
if (/*@cc_on!@*/false || (!!window.MSInputMethodContext && !!document.documentMode))
    document.write(
	'<style>'+
		'html{'+
			'overflow-x: hidden !important;'+
			'overflow-y: hidden !important;'+
		'}'+
		'.kill-ie{'+
			'text-align:center;'+
			'height: 100%;'+
			'margin-top: 15%;'+
			'margin-bottom: 5500%;'+
		'}'+
		'#l_header,#l_body{'+
			'display: none;'+
		'}'+
	'</style>'+
    '<div class="kill-ie">'+
        '<h2><b>抱歉，您的浏览器无法访问本站</b></h2>'+
        '<h3>微软已经于2016年终止了对 Internet Explorer (IE) 10 及更早版本的支持，<br/>'+
        '继续使用存在极大的安全隐患，请使用当代主流的浏览器进行访问。</h3><br/>'+
        '<a target="_blank" rel="noopener" href="https://www.microsoft.com/zh-cn/WindowsForBusiness/End-of-IE-support"><strong>了解详情 ></strong></a>'+
    '</div>');
</script>


<noscript>
	<style>
		html{
			overflow-x: hidden !important;
			overflow-y: hidden !important;
		}
		.kill-noscript{
			text-align:center;
			height: 100%;
			margin-top: 15%;
			margin-bottom: 5500%;
		}
		#l_header,#l_body{
			display: none;
		}
	</style>
    <div class="kill-noscript">
        <h2><b>抱歉，您的浏览器无法访问本站</b></h2>
        <h3>本页面需要浏览器支持（启用）JavaScript</h3><br/>
        <a target="_blank" rel="noopener" href="https://www.baidu.com/s?wd=启用JavaScript"><strong>了解详情 ></strong></a>
    </div>
</noscript>


  <!-- Custom Files head begin-->
  
  <!-- Custom Files head end-->
</head>
  <body itemscope itemtype="http://schema.org/WebPage">
    <header itemscope itemtype="http://schema.org/WPHeader" id="l_header" class="l_header auto shadow blur show" style='opacity: 0' >
  <div class='container'>
  <div id='wrapper'>
    <div class='nav-sub'>
      <p class="title"></p>
      <ul class='switcher nav-list-h m-phone' id="pjax-header-nav-list">
        <li><a id="s-comment" class="fas fa-comments fa-fw" target="_self"  href="/" onclick="return false;" title="comment"></a></li>
        
          <li><a id="s-toc" class="s-toc fas fa-list fa-fw" target="_self"  href="/" onclick="return false;" title="toc"></a></li>
        
      </ul>
    </div>
		<div class="nav-main">
      
        
        <a class="title flat-box" target="_self" href='/'>
          
            <img no-lazy class='logo' src='https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/blog/Logo-NavBar@3x.png' alt="This is a picture without description" />
          
          
          
        </a>
      

			<div class='menu navigation'>
				<ul class='nav-list-h m-pc'>
          
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/
                  
                  
                  
                    active-action="action-home"
                  >
                  <i class='fas fa-rss fa-fw'></i>首页
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/categories/
                  
                  
                  
                    active-action="action-categories"
                  >
                  <i class='fas fa-folder-open fa-fw'></i>分类
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/tags/
                  
                  
                  
                    active-action="action-tags"
                  >
                  <i class='fas fa-tags fa-fw'></i>标签
                </a>
                
              </li>
            
          
          
            
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/archives/
                  
                  
                  
                    active-action="action-archives"
                  >
                  <i class='fas fa-archive fa-fw'></i>归档
                </a>
                
              </li>
            
          
          
				</ul>
			</div>

      <div class="m_search">
        <form name="searchform" class="form u-search-form">
          <i class="icon fas fa-search fa-fw"></i>
          <input type="text" class="input u-search-input" placeholder="Search..." />
        </form>
      </div>

			<ul class='switcher nav-list-h m-phone'>
				
					<li><a class="s-search fas fa-search fa-fw" target="_self" href="/" onclick="return false;" title="search"></a></li>
				
				<li>
          <a class="s-menu fas fa-bars fa-fw" target="_self" href="/" onclick="return false;" title="menu"></a>
          <ul class="menu-phone list-v navigation white-box">
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/
                  
                  
                  
                    active-action="action-home"
                  >
                  <i class='fas fa-rss fa-fw'></i>首页
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/categories/
                  
                  
                  
                    active-action="action-categories"
                  >
                  <i class='fas fa-folder-open fa-fw'></i>分类
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/tags/
                  
                  
                  
                    active-action="action-tags"
                  >
                  <i class='fas fa-tags fa-fw'></i>标签
                </a>
                
              </li>
            
          
            
              
            
              <li>
                <a class="menuitem flat-box faa-parent animated-hover" href=/archives/
                  
                  
                  
                    active-action="action-archives"
                  >
                  <i class='fas fa-archive fa-fw'></i>归档
                </a>
                
              </li>
            
          
            
          </ul>
        </li>
			</ul>

      <!-- Custom Files header begin -->
      
      <!-- Custom Files header end -->
		</div>
	</div>
  </div>
</header>

    <div id="l_body">
      <div id="l_cover">
  
    
      <!-- see: /layout/_partial/scripts/_ctrl/coverCtrl.ejs -->
      <div id="none" class='cover-wrapper post dock' style="display: none;">
        
  <div class='cover-bg lazyload placeholder' data-bg="/img/bg.png"></div>

<div class='cover-body'>
  <div class='top'>
    
    
      <p class="title">进击的Coder</p>
    
    
  </div>
  <div class='bottom'>
    <div class='menu navigation'>
      <div class='list-h'>
        
          
            <a href="/"
              
              
              active-action="action-home">
              <img src='https://cdn.jsdelivr.net/gh/twitter/twemoji@13.0/assets/svg/1f5c3.svg' alt="This is a picture without description" ><p>首页</p>
            </a>
          
            <a href="/categories/"
              
              
              active-action="action-categories">
              <img src='https://cdn.jsdelivr.net/gh/twitter/twemoji@13.0/assets/svg/1f4f0.svg' alt="This is a picture without description" ><p>分类</p>
            </a>
          
            <a href="/tags/"
              
              
              active-action="action-tags">
              <img src='https://cdn.jsdelivr.net/gh/twitter/twemoji@13.0/assets/svg/1f516.svg' alt="This is a picture without description" ><p>标签</p>
            </a>
          
            <a href="/archives/"
              
              
              active-action="action-archives">
              <img src='https://cdn.jsdelivr.net/gh/twitter/twemoji@13.0/assets/svg/1f9ec.svg' alt="This is a picture without description" ><p>归档</p>
            </a>
          
        
      </div>
    </div>
  </div>
</div>

        <div id="scroll-down" style="display: none;"><i class="fa fa-chevron-down scroll-down-effects"></i></div>
      </div>
    
  
</div>

      <div id="safearea">
        <div class="body-wrapper" id="pjax-container">
          
<div id="l_main" class=''>
  <article itemscope itemtype="http://schema.org/Article" class="article post white-box reveal md shadow article-type-post" id="post" itemscope itemprop="blogPost">
  <link itemprop="mainEntityOfPage" href="https://www.bcoder.top/2021/09/05/Javadoop%E7%B3%BB%E5%88%97%EF%BC%9A%E4%B8%80%E8%A1%8C%E4%B8%80%E8%A1%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E6%B8%85%E6%A5%9A-AbstractQueuedSynchronizer-%E4%BA%8C/">
  <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
    <meta itemprop="name" content="进击的Coder">
  </span>
  


  
    <span hidden>
      <meta itemprop="image" content="https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/blog/favicon/android-chrome-192x192.png">
    </span>
  
  <div class="article-meta" id="top">
    
    
    
      <h1 class="title" itemprop="name headline">
        Javadoop系列：一行一行源码分析清楚 AbstractQueuedSynchronizer (二)
      </h1>
      <div class='new-meta-box'>
        
          
            
<div class='new-meta-item author' itemprop="author" itemscope itemtype="http://schema.org/Person">
  <a itemprop="url" class='author' href="/" rel="nofollow">
    <img itemprop="image" no-lazy src="https://cdn.jsdelivr.net/gh/xaoxuu/cdn-assets/avatar/avatar.png" alt="This is a picture without description" >
    <p itemprop="name">zln</p>
  </a>
</div>

          
        
          
            
  <div class='new-meta-item category'>
    <i class="fas fa-folder-open fa-fw" aria-hidden="true"></i>
    <a class="category-link" href="/categories/JUC/">JUC</a>
    
      <span hidden itemprop="about" itemscope itemtype="http://schema.org/Thing">
        <a href="/categories/JUC/" itemprop="url"><span itemprop="name">JUC</span></a>
      </span>
    
  </div>


          
        
          
            <div class="new-meta-item date" itemprop="dateCreated datePublished" datetime="2021-09-05T15:17:50+08:00">
  <a class='notlink' href="/" onclick="return false;" title="Click me!" >
    <i class="fas fa-calendar-alt fa-fw" aria-hidden="true"></i>
    <p>发布于：Sep 5, 2021</p>
  </a>
</div>

          
        
          
            
  <div class="new-meta-item browse leancloud">
    <a class='notlink' href="/" onclick="return false;" title="Click me!" >
      
      <div id="lc-pv" data-title="Javadoop系列：一行一行源码分析清楚 AbstractQueuedSynchronizer (二)" data-path="/2021/09/05/Javadoop%E7%B3%BB%E5%88%97%EF%BC%9A%E4%B8%80%E8%A1%8C%E4%B8%80%E8%A1%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E6%B8%85%E6%A5%9A-AbstractQueuedSynchronizer-%E4%BA%8C/">
        <i class="fas fa-eye fa-fw" aria-hidden="true"></i>
        <span id='number'><i class="fas fa-circle-notch fa-spin fa-fw" aria-hidden="true"></i></span>
        次浏览
      </div>
    </a>
  </div>


          
        
        <!-- Custom Files topMeta begin-->
        
        <!-- Custom Files topMeta end-->
      </div>
    
  </div>


  <div id="layoutHelper-page-plugins"></div>
  <div id="post-body" itemprop="articleBody">
    <p>文章比较长，信息量比较大，建议在 pc 上阅读。文章标题是为了呼应前文，其实可以单独成文的，主要是希望读者看文章能系统看。</p>
<p>本文关注以下几点内容：</p>
<ol>
<li>深入理解 ReentrantLock 公平锁和非公平锁的区别</li>
<li>深入分析 AbstractQueuedSynchronizer 中的 ConditionObject</li>
<li>深入理解 Java 线程中断和 InterruptedException 异常</li>
</ol>
<p>基本上本文把以上几点都说清楚了，我假设读者看过<a target="_blank" rel="noopener" href="http://hongjiev.github.io/2017/06/16/AbstractQueuedSynchronizer/">上一篇文章中对 AbstractQueuedSynchronizer 的介绍 </a>，当然如果你已经熟悉 AQS 中的独占锁了，那也可以直接看这篇。各小节之间基本上没什么关系，大家可以只关注自己感兴趣的部分。</p>
<p>其实这篇文章的信息量很大，初学者估计<strong>至少要 1 小时</strong>才能看完，希望本文对得起大家的时间。</p>
<h2 id="公平锁和非公平锁"><a href="#公平锁和非公平锁" class="headerlink" title="公平锁和非公平锁"></a>公平锁和非公平锁</h2><div class="story post-story"><p>ReentrantLock 默认采用非公平锁，除非你在构造方法中传入参数 true 。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ReentrantLock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 默认非公平锁</span></span><br><span class="line">    sync = <span class="keyword">new</span> NonfairSync();</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ReentrantLock</span><span class="params">(<span class="keyword">boolean</span> fair)</span> </span>&#123;</span><br><span class="line">    sync = fair ? <span class="keyword">new</span> FairSync() : <span class="keyword">new</span> NonfairSync();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>公平锁的 lock 方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">FairSync</span> <span class="keyword">extends</span> <span class="title">Sync</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        acquire(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// AbstractQueuedSynchronizer.acquire(int arg)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">acquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!tryAcquire(arg) &amp;&amp;</span><br><span class="line">            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line">            selfInterrupt();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tryAcquire</span><span class="params">(<span class="keyword">int</span> acquires)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> Thread current = Thread.currentThread();</span><br><span class="line">        <span class="keyword">int</span> c = getState();</span><br><span class="line">        <span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 1. 和非公平锁相比，这里多了一个判断：是否有线程在等待</span></span><br><span class="line">            <span class="keyword">if</span> (!hasQueuedPredecessors() &amp;&amp;</span><br><span class="line">                compareAndSetState(<span class="number">0</span>, acquires)) &#123;</span><br><span class="line">                setExclusiveOwnerThread(current);</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) &#123;</span><br><span class="line">            <span class="keyword">int</span> nextc = c + acquires;</span><br><span class="line">            <span class="keyword">if</span> (nextc &lt; <span class="number">0</span>)</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">&quot;Maximum lock count exceeded&quot;</span>);</span><br><span class="line">            setState(nextc);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>非公平锁的 lock 方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">NonfairSync</span> <span class="keyword">extends</span> <span class="title">Sync</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 2. 和公平锁相比，这里会直接先进行一次CAS，成功就返回了</span></span><br><span class="line">        <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))</span><br><span class="line">            setExclusiveOwnerThread(Thread.currentThread());</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            acquire(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// AbstractQueuedSynchronizer.acquire(int arg)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">acquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!tryAcquire(arg) &amp;&amp;</span><br><span class="line">            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line">            selfInterrupt();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">tryAcquire</span><span class="params">(<span class="keyword">int</span> acquires)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> nonfairTryAcquire(acquires);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Performs non-fair tryLock.  tryAcquire is implemented in</span></span><br><span class="line"><span class="comment"> * subclasses, but both need nonfair try for trylock method.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">nonfairTryAcquire</span><span class="params">(<span class="keyword">int</span> acquires)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> Thread current = Thread.currentThread();</span><br><span class="line">    <span class="keyword">int</span> c = getState();</span><br><span class="line">    <span class="keyword">if</span> (c == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 这里没有对阻塞队列进行判断</span></span><br><span class="line">        <span class="keyword">if</span> (compareAndSetState(<span class="number">0</span>, acquires)) &#123;</span><br><span class="line">            setExclusiveOwnerThread(current);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (current == getExclusiveOwnerThread()) &#123;</span><br><span class="line">        <span class="keyword">int</span> nextc = c + acquires;</span><br><span class="line">        <span class="keyword">if</span> (nextc &lt; <span class="number">0</span>) <span class="comment">// overflow</span></span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">&quot;Maximum lock count exceeded&quot;</span>);</span><br><span class="line">        setState(nextc);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>总结：公平锁和非公平锁只有两处不同：</p>
<ol>
<li>非公平锁在调用 lock 后，首先就会调用 CAS 进行一次抢锁，如果这个时候恰巧锁没有被占用，那么直接就获取到锁返回了。</li>
<li>非公平锁在 CAS 失败后，和公平锁一样都会进入到 tryAcquire 方法，在 tryAcquire 方法中，如果发现锁这个时候被释放了（state == 0），非公平锁会直接 CAS 抢锁，但是公平锁会判断等待队列是否有线程处于等待状态，如果有则不去抢锁，乖乖排到后面。</li>
</ol>
<p>公平锁和非公平锁就这两点区别，如果这两次 CAS 都不成功，那么后面非公平锁和公平锁是一样的，都要进入到阻塞队列等待唤醒。</p>
<p>相对来说，非公平锁会有更好的性能，因为它的吞吐量比较大。当然，非公平锁让获取锁的时间变得更加不确定，可能会导致在阻塞队列中的线程长期处于饥饿状态。</p>
</div><h2 id="Condition"><a href="#Condition" class="headerlink" title="Condition"></a>Condition</h2><div class="story post-story"><p>Tips: 这里重申一下，要看懂这个，必须要先看懂上一篇关于 <a target="_blank" rel="noopener" href="http://hongjiev.github.io/2017/06/16/AbstractQueuedSynchronizer/">AbstractQueuedSynchronizer</a> 的介绍，或者你已经有相关的知识了，否则这节肯定是看不懂的。</p>
<p>我们先来看看 Condition 的使用场景，Condition 经常可以用在<strong>生产者-消费者</strong>的场景中，请看 Doug Lea 给出的这个例子：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.Condition;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.Lock;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.ReentrantLock;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BoundedBuffer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> Lock lock = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line">    <span class="comment">// condition 依赖于 lock 来产生</span></span><br><span class="line">    <span class="keyword">final</span> Condition notFull = lock.newCondition();</span><br><span class="line">    <span class="keyword">final</span> Condition notEmpty = lock.newCondition();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> Object[] items = <span class="keyword">new</span> Object[<span class="number">100</span>];</span><br><span class="line">    <span class="keyword">int</span> putptr, takeptr, count;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 生产</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">put</span><span class="params">(Object x)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (count == items.length)</span><br><span class="line">                notFull.await();  <span class="comment">// 队列已满，等待，直到 not full 才能继续生产</span></span><br><span class="line">            items[putptr] = x;</span><br><span class="line">            <span class="keyword">if</span> (++putptr == items.length) putptr = <span class="number">0</span>;</span><br><span class="line">            ++count;</span><br><span class="line">            notEmpty.signal(); <span class="comment">// 生产成功，队列已经 not empty 了，发个通知出去</span></span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 消费</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">take</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (count == <span class="number">0</span>)</span><br><span class="line">                notEmpty.await(); <span class="comment">// 队列为空，等待，直到队列 not empty，才能继续消费</span></span><br><span class="line">            Object x = items[takeptr];</span><br><span class="line">            <span class="keyword">if</span> (++takeptr == items.length) takeptr = <span class="number">0</span>;</span><br><span class="line">            --count;</span><br><span class="line">            notFull.signal(); <span class="comment">// 被我消费掉一个，队列 not full 了，发个通知出去</span></span><br><span class="line">            <span class="keyword">return</span> x;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<blockquote>
<p>1、我们可以看到，在使用 condition 时，必须先持有相应的锁。这个和 Object 类中的方法有相似的语义，需要先持有某个对象的监视器锁才可以执行 wait(), notify() 或 notifyAll() 方法。</p>
<p>2、ArrayBlockingQueue 采用这种方式实现了生产者-消费者，所以请只把这个例子当做学习例子，实际生产中可以直接使用 ArrayBlockingQueue</p>
</blockquote>
<p>我们常用 obj.wait()，obj.notify() 或 obj.notifyAll() 来实现相似的功能，但是，它们是基于对象的监视器锁的。需要深入了解这几个方法的读者，可以参考我的另一篇文章《<a target="_blank" rel="noopener" href="http://hongjiev.github.io/2017/07/05/Threads-And-Locks-md/">深入分析 java 8 编程语言规范：Threads and Locks</a>》。而这里说的 Condition 是基于 ReentrantLock 实现的，而 ReentrantLock 是依赖于 AbstractQueuedSynchronizer 实现的。</p>
<p>在往下看之前，读者心里要有一个整体的概念。condition 是依赖于 ReentrantLock  的，不管是调用 await 进入等待还是 signal 唤醒，<strong>都必须获取到锁才能进行操作</strong>。</p>
<p>每个 ReentrantLock  实例可以通过调用多次 newCondition 产生多个 ConditionObject 的实例：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> ConditionObject <span class="title">newCondition</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 实例化一个 ConditionObject</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> ConditionObject();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>我们首先来看下我们关注的 Condition 的实现类 <code>AbstractQueuedSynchronizer</code> 类中的 <code>ConditionObject</code>。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ConditionObject</span> <span class="keyword">implements</span> <span class="title">Condition</span>, <span class="title">java</span>.<span class="title">io</span>.<span class="title">Serializable</span> </span>&#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">1173984872572414699L</span>;</span><br><span class="line">        <span class="comment">// 条件队列的第一个节点</span></span><br><span class="line">  		<span class="comment">// 不要管这里的关键字 transient，是不参与序列化的意思</span></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">transient</span> Node firstWaiter;</span><br><span class="line">        <span class="comment">// 条件队列的最后一个节点</span></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">transient</span> Node lastWaiter;</span><br><span class="line">        ......</span><br></pre></td></tr></table></figure>


<p>在上一篇介绍 AQS 的时候，我们有一个<strong>阻塞队列</strong>，用于保存等待获取锁的线程的队列。这里我们引入另一个概念，叫<strong>条件队列</strong>（condition queue），我画了一张简单的图用来说明这个。</p>
<blockquote>
<p>这里的阻塞队列如果叫做同步队列（sync queue）其实比较贴切，不过为了和前篇呼应，我就继续使用阻塞队列了。记住这里的两个概念，<strong>阻塞队列</strong>和<strong>条件队列</strong>。</p>
</blockquote>
<p><img src="https://www.javadoop.com/blogimages/AbstractQueuedSynchronizer-2/aqs2-2.png#height=549&id=q2pMT&originHeight=1052&originWidth=1534&originalType=binary&ratio=1&status=done&style=none&width=800" class="lazyload" data-srcset="https://www.javadoop.com/blogimages/AbstractQueuedSynchronizer-2/aqs2-2.png#height=549&id=q2pMT&originHeight=1052&originWidth=1534&originalType=binary&ratio=1&status=done&style=none&width=800" srcset="" alt="This is a picture without description" ></p>
<blockquote>
<p>这里，我们简单回顾下 Node 的属性：</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">volatile</span> <span class="keyword">int</span> waitStatus; <span class="comment">// 可取值 0、CANCELLED(1)、SIGNAL(-1)、CONDITION(-2)、PROPAGATE(-3)</span></span><br><span class="line"><span class="keyword">volatile</span> Node prev;</span><br><span class="line"><span class="keyword">volatile</span> Node next;</span><br><span class="line"><span class="keyword">volatile</span> Thread thread;</span><br><span class="line">Node nextWaiter;</span><br></pre></td></tr></table></figure>


<blockquote>
<p>prev 和 next 用于实现阻塞队列的双向链表，这里的 nextWaiter 用于实现条件队列的单向链表</p>
</blockquote>
<p>基本上，把这张图看懂，你也就知道 condition 的处理流程了。所以，我先简单解释下这图，然后再具体地解释代码实现。</p>
<ol>
<li>条件队列和阻塞队列的节点，都是 Node 的实例，因为条件队列的节点是需要转移到阻塞队列中去的；</li>
<li>我们知道一个 ReentrantLock 实例可以通过多次调用 newCondition() 来产生多个 Condition 实例，这里对应 condition1 和 condition2。注意，ConditionObject 只有两个属性 firstWaiter 和 lastWaiter；</li>
<li>每个 condition 有一个关联的<strong>条件队列</strong>，如线程 1 调用 <code>condition1.await()</code> 方法即可将当前线程 1 包装成 Node 后加入到条件队列中，然后阻塞在这里，不继续往下执行，条件队列是一个单向链表；</li>
<li>调用<code>condition1.signal()</code> 触发一次唤醒，此时唤醒的是队头，会将condition1 对应的<strong>条件队列</strong>的 firstWaiter（队头） 移到<strong>阻塞队列的队尾</strong>，等待获取锁，获取锁后 await 方法才能返回，继续往下执行。</li>
</ol>
<p>上面的 2-&gt;3-&gt;4 描述了一个最简单的流程，没有考虑中断、signalAll、还有带有超时参数的 await 方法等，不过把这里弄懂是这节的主要目的。</p>
<p>同时，从图中也可以很直观地看出，哪些操作是线程安全的，哪些操作是线程不安全的。</p>
<p>这个图看懂后，下面的代码分析就简单了。</p>
<p>接下来，我们一步步按照流程来走代码分析，我们先来看看 wait 方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 首先，这个方法是可被中断的，不可被中断的是另一个方法 awaitUninterruptibly()</span></span><br><span class="line"><span class="comment">// 这个方法会阻塞，直到调用 signal 方法（指 signal() 和 signalAll()，下同），或被中断</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">await</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">    <span class="comment">// 老规矩，既然该方法要响应中断，那么在最开始就判断中断状态</span></span><br><span class="line">    <span class="keyword">if</span> (Thread.interrupted())</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> InterruptedException();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 添加到 condition 的条件队列中</span></span><br><span class="line">    Node node = addConditionWaiter();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 释放锁，返回值是释放锁之前的 state 值</span></span><br><span class="line">    <span class="comment">// await() 之前，当前线程是必须持有锁的，这里肯定要释放掉</span></span><br><span class="line">    <span class="keyword">int</span> savedState = fullyRelease(node);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">int</span> interruptMode = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// 这里退出循环有两种情况，之后再仔细分析</span></span><br><span class="line">    <span class="comment">// 1. isOnSyncQueue(node) 返回 true，即当前 node 已经转移到阻塞队列了</span></span><br><span class="line">    <span class="comment">// 2. checkInterruptWhileWaiting(node) != 0 会到 break，然后退出循环，代表的是线程中断</span></span><br><span class="line">    <span class="keyword">while</span> (!isOnSyncQueue(node)) &#123;</span><br><span class="line">        LockSupport.park(<span class="keyword">this</span>);</span><br><span class="line">        <span class="keyword">if</span> ((interruptMode = checkInterruptWhileWaiting(node)) != <span class="number">0</span>)</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 被唤醒后，将进入阻塞队列，等待获取锁</span></span><br><span class="line">    <span class="keyword">if</span> (acquireQueued(node, savedState) &amp;&amp; interruptMode != THROW_IE)</span><br><span class="line">        interruptMode = REINTERRUPT;</span><br><span class="line">    <span class="keyword">if</span> (node.nextWaiter != <span class="keyword">null</span>) <span class="comment">// clean up if cancelled</span></span><br><span class="line">        unlinkCancelledWaiters();</span><br><span class="line">    <span class="keyword">if</span> (interruptMode != <span class="number">0</span>)</span><br><span class="line">        reportInterruptAfterWait(interruptMode);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>其实，我大体上也把整个 await 过程说得十之八九了，下面我们分步把上面的几个点用源码说清楚。</p>
<h3 id="1-将节点加入到条件队列"><a href="#1-将节点加入到条件队列" class="headerlink" title="1. 将节点加入到条件队列"></a>1. 将节点加入到条件队列</h3><p>addConditionWaiter() 是将当前节点加入到条件队列，看图我们知道，这种条件队列内的操作是线程安全的。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 将当前线程对应的节点入队，插入队尾</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> Node <span class="title">addConditionWaiter</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    Node t = lastWaiter;</span><br><span class="line">    <span class="comment">// 如果条件队列的最后一个节点取消了，将其清除出去</span></span><br><span class="line">    <span class="comment">// 为什么这里把 waitStatus 不等于 Node.CONDITION，就判定为该节点发生了取消排队？</span></span><br><span class="line">    <span class="keyword">if</span> (t != <span class="keyword">null</span> &amp;&amp; t.waitStatus != Node.CONDITION) &#123;</span><br><span class="line">        <span class="comment">// 这个方法会遍历整个条件队列，然后会将已取消的所有节点清除出队列</span></span><br><span class="line">        unlinkCancelledWaiters();</span><br><span class="line">        t = lastWaiter;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// node 在初始化的时候，指定 waitStatus 为 Node.CONDITION</span></span><br><span class="line">    Node node = <span class="keyword">new</span> Node(Thread.currentThread(), Node.CONDITION);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// t 此时是 lastWaiter，队尾</span></span><br><span class="line">    <span class="comment">// 如果队列为空</span></span><br><span class="line">    <span class="keyword">if</span> (t == <span class="keyword">null</span>)</span><br><span class="line">        firstWaiter = node;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        t.nextWaiter = node;</span><br><span class="line">    lastWaiter = node;</span><br><span class="line">    <span class="keyword">return</span> node;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>上面的这块代码很简单，就是将当前线程进入到条件队列的队尾。</p>
<p>在addWaiter 方法中，有一个 unlinkCancelledWaiters() 方法，该方法用于清除队列中已经取消等待的节点。</p>
<p>当 await 的时候如果发生了取消操作（这点之后会说），或者是在节点入队的时候，发现最后一个节点是被取消的，会调用一次这个方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 等待队列是一个单向链表，遍历链表将已经取消等待的节点清除出去</span></span><br><span class="line"><span class="comment">// 纯属链表操作，很好理解，看不懂多看几遍就可以了</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">unlinkCancelledWaiters</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    Node t = firstWaiter;</span><br><span class="line">    Node trail = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">while</span> (t != <span class="keyword">null</span>) &#123;</span><br><span class="line">        Node next = t.nextWaiter;</span><br><span class="line">        <span class="comment">// 如果节点的状态不是 Node.CONDITION 的话，这个节点就是被取消的</span></span><br><span class="line">        <span class="keyword">if</span> (t.waitStatus != Node.CONDITION) &#123;</span><br><span class="line">            t.nextWaiter = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">if</span> (trail == <span class="keyword">null</span>)</span><br><span class="line">                firstWaiter = next;</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                trail.nextWaiter = next;</span><br><span class="line">            <span class="keyword">if</span> (next == <span class="keyword">null</span>)</span><br><span class="line">                lastWaiter = trail;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            trail = t;</span><br><span class="line">        t = next;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h3 id="2-完全释放独占锁"><a href="#2-完全释放独占锁" class="headerlink" title="2. 完全释放独占锁"></a>2. 完全释放独占锁</h3><p>回到 wait 方法，节点入队了以后，会调用 <code>int savedState = fullyRelease(node);</code> 方法释放锁，注意，这里是完全释放独占锁（fully release），因为 ReentrantLock 是可以重入的。</p>
<blockquote>
<p>考虑一下这里的 savedState。如果在 condition1.await() 之前，假设线程先执行了 2 次 lock() 操作，那么 state 为 2，我们理解为该线程持有 2 把锁，这里 await() 方法必须将 state 设置为 0，然后再进入挂起状态，这样其他线程才能持有锁。当它被唤醒的时候，它需要重新持有 2 把锁，才能继续下去。</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 首先，我们要先观察到返回值 savedState 代表 release 之前的 state 值</span></span><br><span class="line"><span class="comment">// 对于最简单的操作：先 lock.lock()，然后 condition1.await()。</span></span><br><span class="line"><span class="comment">// 		那么 state 经过这个方法由 1 变为 0，锁释放，此方法返回 1</span></span><br><span class="line"><span class="comment">// 		相应的，如果 lock 重入了 n 次，savedState == n</span></span><br><span class="line"><span class="comment">// 如果这个方法失败，会将节点设置为&quot;取消&quot;状态，并抛出异常 IllegalMonitorStateException</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">int</span> <span class="title">fullyRelease</span><span class="params">(Node node)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">boolean</span> failed = <span class="keyword">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">int</span> savedState = getState();</span><br><span class="line">        <span class="comment">// 这里使用了当前的 state 作为 release 的参数，也就是完全释放掉锁，将 state 置为 0</span></span><br><span class="line">        <span class="keyword">if</span> (release(savedState)) &#123;</span><br><span class="line">            failed = <span class="keyword">false</span>;</span><br><span class="line">            <span class="keyword">return</span> savedState;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalMonitorStateException();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (failed)</span><br><span class="line">            node.waitStatus = Node.CANCELLED;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<blockquote>
<p>考虑一下，如果一个线程在不持有 lock 的基础上，就去调用 condition1.await() 方法，它能进入条件队列，但是在上面的这个方法中，由于它不持有锁，release(savedState) 这个方法肯定要返回 false，进入到异常分支，然后进入 finally 块设置 <code>node.waitStatus = Node.CANCELLED</code>，这个已经入队的节点之后会被后继的节点”请出去“。</p>
</blockquote>
<h3 id="3-等待进入阻塞队列"><a href="#3-等待进入阻塞队列" class="headerlink" title="3. 等待进入阻塞队列"></a>3. 等待进入阻塞队列</h3><p>释放掉锁以后，接下来是这段，这边会自旋，如果发现自己还没到阻塞队列，那么挂起，等待被转移到阻塞队列。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> interruptMode = <span class="number">0</span>;</span><br><span class="line"><span class="comment">// 如果不在阻塞队列中，注意了，是阻塞队列</span></span><br><span class="line"><span class="keyword">while</span> (!isOnSyncQueue(node)) &#123;</span><br><span class="line">    <span class="comment">// 线程挂起</span></span><br><span class="line">    LockSupport.park(<span class="keyword">this</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 这里可以先不用看了，等看到它什么时候被 unpark 再说</span></span><br><span class="line">    <span class="keyword">if</span> ((interruptMode = checkInterruptWhileWaiting(node)) != <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>isOnSyncQueue(Node node) 用于判断节点是否已经转移到阻塞队列了：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在节点入条件队列的时候，初始化时设置了 waitStatus = Node.CONDITION</span></span><br><span class="line"><span class="comment">// 前面我提到，signal 的时候需要将节点从条件队列移到阻塞队列，</span></span><br><span class="line"><span class="comment">// 这个方法就是判断 node 是否已经移动到阻塞队列了</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">isOnSyncQueue</span><span class="params">(Node node)</span> </span>&#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 移动过去的时候，node 的 waitStatus 会置为 0，这个之后在说 signal 方法的时候会说到</span></span><br><span class="line">    <span class="comment">// 如果 waitStatus 还是 Node.CONDITION，也就是 -2，那肯定就是还在条件队列中</span></span><br><span class="line">    <span class="comment">// 如果 node 的前驱 prev 指向还是 null，说明肯定没有在 阻塞队列(prev是阻塞队列链表中使用的)</span></span><br><span class="line">    <span class="keyword">if</span> (node.waitStatus == Node.CONDITION || node.prev == <span class="keyword">null</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    <span class="comment">// 如果 node 已经有后继节点 next 的时候，那肯定是在阻塞队列了</span></span><br><span class="line">    <span class="keyword">if</span> (node.next != <span class="keyword">null</span>) </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 下面这个方法从阻塞队列的队尾开始从后往前遍历找，如果找到相等的，说明在阻塞队列，否则就是不在阻塞队列</span></span><br><span class="line">  </span><br><span class="line">    <span class="comment">// 可以通过判断 node.prev() != null 来推断出 node 在阻塞队列吗？答案是：不能。</span></span><br><span class="line">    <span class="comment">// 这个可以看上篇 AQS 的入队方法，首先设置的是 node.prev 指向 tail，</span></span><br><span class="line">    <span class="comment">// 然后是 CAS 操作将自己设置为新的 tail，可是这次的 CAS 是可能失败的。</span></span><br><span class="line">  </span><br><span class="line">    <span class="keyword">return</span> findNodeFromTail(node);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从阻塞队列的队尾往前遍历，如果找到，返回 true</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">findNodeFromTail</span><span class="params">(Node node)</span> </span>&#123;</span><br><span class="line">    Node t = tail;</span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        <span class="keyword">if</span> (t == node)</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (t == <span class="keyword">null</span>)</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        t = t.prev;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>回到前面的循环，isOnSyncQueue(node) 返回 false 的话，那么进到 <code>LockSupport.park(this);</code> 这里线程挂起。</p>
<h3 id="4-signal-唤醒线程，转移到阻塞队列"><a href="#4-signal-唤醒线程，转移到阻塞队列" class="headerlink" title="4. signal 唤醒线程，转移到阻塞队列"></a>4. signal 唤醒线程，转移到阻塞队列</h3><p>为了大家理解，这里我们先看唤醒操作，因为刚刚到 <code>LockSupport.park(this);</code> 把线程挂起了，等待唤醒。</p>
<p>唤醒操作通常由另一个线程来操作，就像生产者-消费者模式中，如果线程因为等待消费而挂起，那么当生产者生产了一个东西后，会调用 signal 唤醒正在等待的线程来消费。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 唤醒等待了最久的线程</span></span><br><span class="line"><span class="comment">// 其实就是，将这个线程对应的 node 从条件队列转移到阻塞队列</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">signal</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 调用 signal 方法的线程必须持有当前的独占锁</span></span><br><span class="line">    <span class="keyword">if</span> (!isHeldExclusively())</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalMonitorStateException();</span><br><span class="line">    Node first = firstWaiter;</span><br><span class="line">    <span class="keyword">if</span> (first != <span class="keyword">null</span>)</span><br><span class="line">        doSignal(first);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从条件队列队头往后遍历，找出第一个需要转移的 node</span></span><br><span class="line"><span class="comment">// 因为前面我们说过，有些线程会取消排队，但是可能还在队列中</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">doSignal</span><span class="params">(Node first)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">do</span> &#123;</span><br><span class="line">      	<span class="comment">// 将 firstWaiter 指向 first 节点后面的第一个，因为 first 节点马上要离开了</span></span><br><span class="line">        <span class="comment">// 如果将 first 移除后，后面没有节点在等待了，那么需要将 lastWaiter 置为 null</span></span><br><span class="line">        <span class="keyword">if</span> ( (firstWaiter = first.nextWaiter) == <span class="keyword">null</span>)</span><br><span class="line">            lastWaiter = <span class="keyword">null</span>;</span><br><span class="line">        <span class="comment">// 因为 first 马上要被移到阻塞队列了，和条件队列的链接关系在这里断掉</span></span><br><span class="line">        first.nextWaiter = <span class="keyword">null</span>;</span><br><span class="line">    &#125; <span class="keyword">while</span> (!transferForSignal(first) &amp;&amp;</span><br><span class="line">             (first = firstWaiter) != <span class="keyword">null</span>);</span><br><span class="line">      <span class="comment">// 这里 while 循环，如果 first 转移不成功，那么选择 first 后面的第一个节点进行转移，依此类推</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将节点从条件队列转移到阻塞队列</span></span><br><span class="line"><span class="comment">// true 代表成功转移</span></span><br><span class="line"><span class="comment">// false 代表在 signal 之前，节点已经取消了</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">transferForSignal</span><span class="params">(Node node)</span> </span>&#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// CAS 如果失败，说明此 node 的 waitStatus 已不是 Node.CONDITION，说明节点已经取消，</span></span><br><span class="line">    <span class="comment">// 既然已经取消，也就不需要转移了，方法返回，转移后面一个节点</span></span><br><span class="line">    <span class="comment">// 否则，将 waitStatus 置为 0</span></span><br><span class="line">    <span class="keyword">if</span> (!compareAndSetWaitStatus(node, Node.CONDITION, <span class="number">0</span>))</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">  </span><br><span class="line">    <span class="comment">// enq(node): 自旋进入阻塞队列的队尾</span></span><br><span class="line">    <span class="comment">// 注意，这里的返回值 p 是 node 在阻塞队列的前驱节点</span></span><br><span class="line">    Node p = enq(node);</span><br><span class="line">    <span class="keyword">int</span> ws = p.waitStatus;</span><br><span class="line">    <span class="comment">// ws &gt; 0 说明 node 在阻塞队列中的前驱节点取消了等待锁，直接唤醒 node 对应的线程。唤醒之后会怎么样，后面再解释</span></span><br><span class="line">    <span class="comment">// 如果 ws &lt;= 0, 那么 compareAndSetWaitStatus 将会被调用，上篇介绍的时候说过，节点入队后，需要把前驱节点的状态设为 Node.SIGNAL(-1)</span></span><br><span class="line">    <span class="keyword">if</span> (ws &gt; <span class="number">0</span> || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))</span><br><span class="line">        <span class="comment">// 如果前驱节点取消或者 CAS 失败，会进到这里唤醒线程，之后的操作看下一节</span></span><br><span class="line">        LockSupport.unpark(node.thread);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>正常情况下，<code>ws &gt; 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)</code> 这句中，ws &lt;= 0，而且 <code>compareAndSetWaitStatus(p, ws, Node.SIGNAL)</code> 会返回 true，所以一般也不会进去 if 语句块中唤醒 node 对应的线程。然后这个方法返回 true，也就意味着 signal 方法结束了，节点进入了阻塞队列。</p>
<p>假设发生了阻塞队列中的前驱节点取消等待，或者 CAS 失败，只要唤醒线程，让其进到下一步即可。</p>
<h3 id="5-唤醒后检查中断状态"><a href="#5-唤醒后检查中断状态" class="headerlink" title="5. 唤醒后检查中断状态"></a>5. 唤醒后检查中断状态</h3><p>上一步 signal 之后，我们的线程由条件队列转移到了阻塞队列，之后就准备获取锁了。只要重新获取到锁了以后，继续往下执行。</p>
<p>等线程从挂起中恢复过来，继续往下看</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> interruptMode = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">while</span> (!isOnSyncQueue(node)) &#123;</span><br><span class="line">    <span class="comment">// 线程挂起</span></span><br><span class="line">    LockSupport.park(<span class="keyword">this</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> ((interruptMode = checkInterruptWhileWaiting(node)) != <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>先解释下 interruptMode。interruptMode 可以取值为 REINTERRUPT（1），THROW_IE（-1），0</p>
<ul>
<li>REINTERRUPT： 代表 await 返回的时候，需要重新设置中断状态</li>
<li>THROW_IE： 代表 await 返回的时候，需要抛出 InterruptedException 异常</li>
<li>0 ：说明在 await 期间，没有发生中断</li>
</ul>
<p>有以下三种情况会让 LockSupport.park(this); 这句返回继续往下执行：</p>
<ol>
<li>常规路径。signal -&gt; 转移节点到阻塞队列 -&gt; 获取了锁（unpark）</li>
<li>线程中断。在 park 的时候，另外一个线程对这个线程进行了中断</li>
<li>signal 的时候我们说过，转移以后的前驱节点取消了，或者对前驱节点的CAS操作失败了</li>
<li>假唤醒。这个也是存在的，和 Object.wait() 类似，都有这个问题</li>
</ol>
<p>线程唤醒后第一步是调用 checkInterruptWhileWaiting(node) 这个方法，此方法用于判断是否在线程挂起期间发生了中断，如果发生了中断，是 signal 调用之前中断的，还是 signal 之后发生的中断。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 如果在 signal 之前已经中断，返回 THROW_IE</span></span><br><span class="line"><span class="comment">// 2. 如果是 signal 之后中断，返回 REINTERRUPT</span></span><br><span class="line"><span class="comment">// 3. 没有发生中断，返回 0</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">checkInterruptWhileWaiting</span><span class="params">(Node node)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Thread.interrupted() ?</span><br><span class="line">        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :</span><br><span class="line">        <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<blockquote>
<p>Thread.interrupted()：如果当前线程已经处于中断状态，那么该方法返回 true，同时将中断状态重置为 false，所以，才有后续的 <code>重新中断（REINTERRUPT）</code> 的使用。</p>
</blockquote>
<p>看看怎么判断是 signal 之前还是之后发生的中断：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 只有线程处于中断状态，才会调用此方法</span></span><br><span class="line"><span class="comment">// 如果需要的话，将这个已经取消等待的节点转移到阻塞队列</span></span><br><span class="line"><span class="comment">// 返回 true：如果此线程在 signal 之前被取消，</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">transferAfterCancelledWait</span><span class="params">(Node node)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 用 CAS 将节点状态设置为 0 </span></span><br><span class="line">    <span class="comment">// 如果这步 CAS 成功，说明是 signal 方法之前发生的中断，因为如果 signal 先发生的话，signal 中会将 waitStatus 设置为 0</span></span><br><span class="line">    <span class="keyword">if</span> (compareAndSetWaitStatus(node, Node.CONDITION, <span class="number">0</span>)) &#123;</span><br><span class="line">        <span class="comment">// 将节点放入阻塞队列</span></span><br><span class="line">        <span class="comment">// 这里我们看到，即使中断了，依然会转移到阻塞队列</span></span><br><span class="line">        enq(node);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 到这里是因为 CAS 失败，肯定是因为 signal 方法已经将 waitStatus 设置为了 0</span></span><br><span class="line">    <span class="comment">// signal 方法会将节点转移到阻塞队列，但是可能还没完成，这边自旋等待其完成</span></span><br><span class="line">    <span class="comment">// 当然，这种事情还是比较少的吧：signal 调用之后，没完成转移之前，发生了中断</span></span><br><span class="line">    <span class="keyword">while</span> (!isOnSyncQueue(node))</span><br><span class="line">        Thread.yield();</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<blockquote>
<p>这里再说一遍，即使发生了中断，节点依然会转移到阻塞队列。</p>
</blockquote>
<p>到这里，大家应该都知道这个 while 循环怎么退出了吧。要么中断，要么转移成功。</p>
<p>这里描绘了一个场景，本来有个线程，它是排在条件队列的后面的，但是因为它被中断了，那么它会被唤醒，然后它发现自己不是被 signal 的那个，但是它会自己主动去进入到阻塞队列。</p>
<h3 id="6-获取独占锁"><a href="#6-获取独占锁" class="headerlink" title="6. 获取独占锁"></a>6. 获取独占锁</h3><p>while 循环出来以后，下面是这段代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (acquireQueued(node, savedState) &amp;&amp; interruptMode != THROW_IE)</span><br><span class="line">    interruptMode = REINTERRUPT;</span><br></pre></td></tr></table></figure>


<p>由于 while 出来后，我们确定节点已经进入了阻塞队列，准备获取锁。</p>
<p>这里的 acquireQueued(node, savedState) 的第一个参数 node 之前已经经过 enq(node) 进入了队列，参数 savedState 是之前释放锁前的 state，这个方法返回的时候，代表当前线程获取了锁，而且 state == savedState了。</p>
<p>注意，前面我们说过，不管有没有发生中断，都会进入到阻塞队列，而 acquireQueued(node, savedState) 的返回值就是代表线程是否被中断。如果返回 true，说明被中断了，而且 interruptMode != THROW_IE，说明在 signal 之前就发生中断了，这里将 interruptMode 设置为 REINTERRUPT，用于待会重新中断。</p>
<p>继续往下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (node.nextWaiter != <span class="keyword">null</span>) <span class="comment">// clean up if cancelled</span></span><br><span class="line">    unlinkCancelledWaiters();</span><br><span class="line"><span class="keyword">if</span> (interruptMode != <span class="number">0</span>)</span><br><span class="line">    reportInterruptAfterWait(interruptMode);</span><br></pre></td></tr></table></figure>


<p>本着一丝不苟的精神，这边说说 <code>node.nextWaiter != null</code> 怎么满足。我前面也说了 signal 的时候会将节点转移到阻塞队列，有一步是 node.nextWaiter = null，将断开节点和条件队列的联系。</p>
<p>可是，<code>在判断发生中断的情况下，是 signal 之前还是之后发生的？</code> 这部分的时候，我也介绍了，如果 signal 之前就中断了，也需要将节点进行转移到阻塞队列，这部分转移的时候，是没有设置 node.nextWaiter = null 的。</p>
<p>之前我们说过，如果有节点取消，也会调用 unlinkCancelledWaiters 这个方法，就是这里了。</p>
<h3 id="7-处理中断状态"><a href="#7-处理中断状态" class="headerlink" title="7. 处理中断状态"></a>7. 处理中断状态</h3><p>到这里，我们终于可以好好说下这个 interruptMode 干嘛用了。</p>
<ul>
<li>0：什么都不做，没有被中断过；</li>
<li>THROW_IE：await 方法抛出 InterruptedException 异常，因为它代表在 await() 期间发生了中断；</li>
<li>REINTERRUPT：重新中断当前线程，因为它代表 await() 期间没有被中断，而是 signal() 以后发生的中断<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">reportInterruptAfterWait</span><span class="params">(<span class="keyword">int</span> interruptMode)</span></span></span><br><span class="line"><span class="function">    <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (interruptMode == THROW_IE)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> InterruptedException();</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (interruptMode == REINTERRUPT)</span><br><span class="line">        selfInterrupt();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ul>
<blockquote>
<p>这个中断状态这部分内容，大家应该都理解了吧，不理解的话，多看几遍就是了。</p>
</blockquote>
<h3 id="带超时机制的-await"><a href="#带超时机制的-await" class="headerlink" title="* 带超时机制的 await"></a>* 带超时机制的 await</h3><p>经过前面的 7 步，整个 ConditionObject 类基本上都分析完了，接下来简单分析下带超时机制的 await 方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">long</span> <span class="title">awaitNanos</span><span class="params">(<span class="keyword">long</span> nanosTimeout)</span> </span></span><br><span class="line"><span class="function">  				<span class="keyword">throws</span> InterruptedException</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">awaitUntil</span><span class="params">(Date deadline)</span></span></span><br><span class="line"><span class="function">                <span class="keyword">throws</span> InterruptedException</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">await</span><span class="params">(<span class="keyword">long</span> time, TimeUnit unit)</span></span></span><br><span class="line"><span class="function">                <span class="keyword">throws</span> InterruptedException</span></span><br></pre></td></tr></table></figure>


<p>这三个方法都差不多，我们就挑一个出来看看吧：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">await</span><span class="params">(<span class="keyword">long</span> time, TimeUnit unit)</span></span></span><br><span class="line"><span class="function">        <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">    <span class="comment">// 等待这么多纳秒</span></span><br><span class="line">    <span class="keyword">long</span> nanosTimeout = unit.toNanos(time);</span><br><span class="line">    <span class="keyword">if</span> (Thread.interrupted())</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> InterruptedException();</span><br><span class="line">    Node node = addConditionWaiter();</span><br><span class="line">    <span class="keyword">int</span> savedState = fullyRelease(node);</span><br><span class="line">    <span class="comment">// 当前时间 + 等待时长 = 过期时间</span></span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">long</span> deadline = System.nanoTime() + nanosTimeout;</span><br><span class="line">    <span class="comment">// 用于返回 await 是否超时</span></span><br><span class="line">    <span class="keyword">boolean</span> timedout = <span class="keyword">false</span>;</span><br><span class="line">    <span class="keyword">int</span> interruptMode = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (!isOnSyncQueue(node)) &#123;</span><br><span class="line">        <span class="comment">// 时间到啦</span></span><br><span class="line">        <span class="keyword">if</span> (nanosTimeout &lt;= <span class="number">0L</span>) &#123;</span><br><span class="line">            <span class="comment">// 这里因为要 break 取消等待了。取消等待的话一定要调用 transferAfterCancelledWait(node) 这个方法</span></span><br><span class="line">            <span class="comment">// 如果这个方法返回 true，在这个方法内，将节点转移到阻塞队列成功</span></span><br><span class="line">            <span class="comment">// 返回 false 的话，说明 signal 已经发生，signal 方法将节点转移了。也就是说没有超时嘛</span></span><br><span class="line">            timedout = transferAfterCancelledWait(node);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// spinForTimeoutThreshold 的值是 1000 纳秒，也就是 1 毫秒</span></span><br><span class="line">        <span class="comment">// 也就是说，如果不到 1 毫秒了，那就不要选择 parkNanos 了，自旋的性能反而更好</span></span><br><span class="line">        <span class="keyword">if</span> (nanosTimeout &gt;= spinForTimeoutThreshold)</span><br><span class="line">            LockSupport.parkNanos(<span class="keyword">this</span>, nanosTimeout);</span><br><span class="line">        <span class="keyword">if</span> ((interruptMode = checkInterruptWhileWaiting(node)) != <span class="number">0</span>)</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="comment">// 得到剩余时间</span></span><br><span class="line">        nanosTimeout = deadline - System.nanoTime();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (acquireQueued(node, savedState) &amp;&amp; interruptMode != THROW_IE)</span><br><span class="line">        interruptMode = REINTERRUPT;</span><br><span class="line">    <span class="keyword">if</span> (node.nextWaiter != <span class="keyword">null</span>)</span><br><span class="line">        unlinkCancelledWaiters();</span><br><span class="line">    <span class="keyword">if</span> (interruptMode != <span class="number">0</span>)</span><br><span class="line">        reportInterruptAfterWait(interruptMode);</span><br><span class="line">    <span class="keyword">return</span> !timedout;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>超时的思路还是很简单的，不带超时参数的 await 是 park，然后等待别人唤醒。而现在就是调用 parkNanos 方法来休眠指定的时间，醒来后判断是否 signal 调用了，调用了就是没有超时，否则就是超时了。超时的话，自己来进行转移到阻塞队列，然后抢锁。</p>
<h3 id="不抛出-InterruptedException-的-await"><a href="#不抛出-InterruptedException-的-await" class="headerlink" title="* 不抛出 InterruptedException 的 await"></a>* 不抛出 InterruptedException 的 await</h3><p>关于 Condition 最后一小节了。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">awaitUninterruptibly</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    Node node = addConditionWaiter();</span><br><span class="line">    <span class="keyword">int</span> savedState = fullyRelease(node);</span><br><span class="line">    <span class="keyword">boolean</span> interrupted = <span class="keyword">false</span>;</span><br><span class="line">    <span class="keyword">while</span> (!isOnSyncQueue(node)) &#123;</span><br><span class="line">        LockSupport.park(<span class="keyword">this</span>);</span><br><span class="line">        <span class="keyword">if</span> (Thread.interrupted())</span><br><span class="line">            interrupted = <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (acquireQueued(node, savedState) || interrupted)</span><br><span class="line">        selfInterrupt();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>很简单，贴一下代码大家就都懂了，我就不废话了。</p>
</div><h2 id="AbstractQueuedSynchronizer-独占锁的取消排队"><a href="#AbstractQueuedSynchronizer-独占锁的取消排队" class="headerlink" title="AbstractQueuedSynchronizer 独占锁的取消排队"></a>AbstractQueuedSynchronizer 独占锁的取消排队</h2><div class="story post-story"><p>这篇文章说的是 AbstractQueuedSynchronizer，只不过好像 Condition 说太多了，赶紧把思路拉回来。</p>
<p>接下来，我想说说怎么取消对锁的竞争？</p>
<p>上篇文章提到过，最重要的方法是这个，我们要在这里面找答案：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">acquireQueued</span><span class="params">(<span class="keyword">final</span> Node node, <span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">boolean</span> failed = <span class="keyword">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">boolean</span> interrupted = <span class="keyword">false</span>;</span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="keyword">final</span> Node p = node.predecessor();</span><br><span class="line">            <span class="keyword">if</span> (p == head &amp;&amp; tryAcquire(arg)) &#123;</span><br><span class="line">                setHead(node);</span><br><span class="line">                p.next = <span class="keyword">null</span>; <span class="comment">// help GC</span></span><br><span class="line">                failed = <span class="keyword">false</span>;</span><br><span class="line">                <span class="keyword">return</span> interrupted;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;</span><br><span class="line">                parkAndCheckInterrupt())</span><br><span class="line">                interrupted = <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (failed)</span><br><span class="line">            cancelAcquire(node);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>首先，到这个方法的时候，节点一定是入队成功的。</p>
<p>我把 parkAndCheckInterrupt() 代码贴过来：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">parkAndCheckInterrupt</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    LockSupport.park(<span class="keyword">this</span>);</span><br><span class="line">    <span class="keyword">return</span> Thread.interrupted();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>这两段代码联系起来看，是不是就清楚了。</p>
<p>如果我们要取消一个线程的排队，我们需要在另外一个线程中对其进行中断。比如某线程调用 lock() 老久不返回，我想中断它。一旦对其进行中断，此线程会从 <code>LockSupport.park(this);</code> 中唤醒，然后 <code>Thread.interrupted();</code> 返回 true。</p>
<p>我们发现一个问题，即使是中断唤醒了这个线程，也就只是设置了 <code>interrupted = true</code> 然后继续下一次循环。而且，由于 <code>Thread.interrupted();</code>  会清除中断状态，第二次进 parkAndCheckInterrupt 的时候，返回会是 false。</p>
<p>所以，我们要看到，在这个方法中，interrupted 只是用来记录是否发生了中断，然后用于方法返回值，其他没有做任何相关事情。</p>
<p>所以，我们看外层方法怎么处理 acquireQueued 返回 false 的情况。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">acquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!tryAcquire(arg) &amp;&amp;</span><br><span class="line">        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line">        selfInterrupt();</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">selfInterrupt</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    Thread.currentThread().interrupt();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>所以说，lock() 方法处理中断的方法就是，你中断归中断，我抢锁还是照样抢锁，几乎没关系，只是我抢到锁了以后，设置线程的中断状态而已，也不抛出任何异常出来。调用者获取锁后，可以去检查是否发生过中断，也可以不理会。</p>
<hr>
<p>来条分割线。有没有被骗的感觉，我说了一大堆，可是和取消没有任何关系啊。</p>
<p>我们来看 ReentrantLock 的另一个 lock 方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">lockInterruptibly</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">    sync.acquireInterruptibly(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>方法上多了个 <code>throws InterruptedException</code> ，经过前面那么多知识的铺垫，这里我就不再啰里啰嗦了。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">acquireInterruptibly</span><span class="params">(<span class="keyword">int</span> arg)</span></span></span><br><span class="line"><span class="function">        <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (Thread.interrupted())</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> InterruptedException();</span><br><span class="line">    <span class="keyword">if</span> (!tryAcquire(arg))</span><br><span class="line">        doAcquireInterruptibly(arg);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>继续往里：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">doAcquireInterruptibly</span><span class="params">(<span class="keyword">int</span> arg)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> Node node = addWaiter(Node.EXCLUSIVE);</span><br><span class="line">    <span class="keyword">boolean</span> failed = <span class="keyword">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="keyword">final</span> Node p = node.predecessor();</span><br><span class="line">            <span class="keyword">if</span> (p == head &amp;&amp; tryAcquire(arg)) &#123;</span><br><span class="line">                setHead(node);</span><br><span class="line">                p.next = <span class="keyword">null</span>; <span class="comment">// help GC</span></span><br><span class="line">                failed = <span class="keyword">false</span>;</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;</span><br><span class="line">                parkAndCheckInterrupt())</span><br><span class="line">                <span class="comment">// 就是这里了，一旦异常，马上结束这个方法，抛出异常。</span></span><br><span class="line">                <span class="comment">// 这里不再只是标记这个方法的返回值代表中断状态</span></span><br><span class="line">                <span class="comment">// 而是直接抛出异常，而且外层也不捕获，一直往外抛到 lockInterruptibly</span></span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> InterruptedException();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="comment">// 如果通过 InterruptedException 异常出去，那么 failed 就是 true 了</span></span><br><span class="line">        <span class="keyword">if</span> (failed)</span><br><span class="line">            cancelAcquire(node);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>既然到这里了，顺便说说 cancelAcquire 这个方法吧：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">cancelAcquire</span><span class="params">(Node node)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// Ignore if node doesn&#x27;t exist</span></span><br><span class="line">    <span class="keyword">if</span> (node == <span class="keyword">null</span>)</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    node.thread = <span class="keyword">null</span>;</span><br><span class="line">    <span class="comment">// Skip cancelled predecessors</span></span><br><span class="line">    <span class="comment">// 找一个合适的前驱。其实就是将它前面的队列中已经取消的节点都”请出去“</span></span><br><span class="line">    Node pred = node.prev;</span><br><span class="line">    <span class="keyword">while</span> (pred.waitStatus &gt; <span class="number">0</span>)</span><br><span class="line">        node.prev = pred = pred.prev;</span><br><span class="line">    <span class="comment">// predNext is the apparent node to unsplice. CASes below will</span></span><br><span class="line">    <span class="comment">// fail if not, in which case, we lost race vs another cancel</span></span><br><span class="line">    <span class="comment">// or signal, so no further action is necessary.</span></span><br><span class="line">    Node predNext = pred.next;</span><br><span class="line">    <span class="comment">// Can use unconditional write instead of CAS here.</span></span><br><span class="line">    <span class="comment">// After this atomic step, other Nodes can skip past us.</span></span><br><span class="line">    <span class="comment">// Before, we are free of interference from other threads.</span></span><br><span class="line">    node.waitStatus = Node.CANCELLED;</span><br><span class="line">    <span class="comment">// If we are the tail, remove ourselves.</span></span><br><span class="line">    <span class="keyword">if</span> (node == tail &amp;&amp; compareAndSetTail(node, pred)) &#123;</span><br><span class="line">        compareAndSetNext(pred, predNext, <span class="keyword">null</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// If successor needs signal, try to set pred&#x27;s next-link</span></span><br><span class="line">        <span class="comment">// so it will get one. Otherwise wake it up to propagate.</span></span><br><span class="line">        <span class="keyword">int</span> ws;</span><br><span class="line">        <span class="keyword">if</span> (pred != head &amp;&amp;</span><br><span class="line">            ((ws = pred.waitStatus) == Node.SIGNAL ||</span><br><span class="line">             (ws &lt;= <span class="number">0</span> &amp;&amp; compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &amp;&amp;</span><br><span class="line">            pred.thread != <span class="keyword">null</span>) &#123;</span><br><span class="line">            Node next = node.next;</span><br><span class="line">            <span class="keyword">if</span> (next != <span class="keyword">null</span> &amp;&amp; next.waitStatus &lt;= <span class="number">0</span>)</span><br><span class="line">                compareAndSetNext(pred, predNext, next);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            unparkSuccessor(node);</span><br><span class="line">        &#125;</span><br><span class="line">        node.next = node; <span class="comment">// help GC</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>其实这个方法没什么好说的，一行行看下去就是了，节点取消，只要把 waitStatus 设置为 Node.CANCELLED，会有非常多的情况被从阻塞队列中请出去，主动或被动。</p>
</div><h2 id="再说-java-线程中断和-InterruptedException-异常"><a href="#再说-java-线程中断和-InterruptedException-异常" class="headerlink" title="再说 java 线程中断和 InterruptedException 异常"></a>再说 java 线程中断和 InterruptedException 异常</h2><div class="story post-story"><p>在之前的文章中，我们接触了大量的中断，这边算是个总结吧。如果你完全熟悉中断了，没有必要再看这节，本节为新手而写。</p>
<h3 id="线程中断"><a href="#线程中断" class="headerlink" title="线程中断"></a>线程中断</h3><p>首先，我们要明白，中断不是类似 linux 里面的命令 kill -9 pid，不是说我们中断某个线程，这个线程就停止运行了。中断代表线程状态，每个线程都关联了一个中断状态，是一个 true 或 false 的 boolean 值，初始值为 false。</p>
<blockquote>
<p>Java 中的中断和操作系统的中断还不一样，这里就按照<strong>状态</strong>来理解吧，不要和操作系统的中断联系在一起</p>
</blockquote>
<p>关于中断状态，我们需要重点关注 Thread 类中的以下几个方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Thread 类中的实例方法，持有线程实例引用即可检测线程中断状态</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isInterrupted</span><span class="params">()</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Thread 中的静态方法，检测调用这个方法的线程是否已经中断</span></span><br><span class="line"><span class="comment">// 注意：这个方法返回中断状态的同时，会将此线程的中断状态重置为 false</span></span><br><span class="line"><span class="comment">// 所以，如果我们连续调用两次这个方法的话，第二次的返回值肯定就是 false 了</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">interrupted</span><span class="params">()</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Thread 类中的实例方法，用于设置一个线程的中断状态为 true</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">interrupt</span><span class="params">()</span> </span>&#123;&#125;</span><br></pre></td></tr></table></figure>


<p>我们说中断一个线程，其实就是设置了线程的 interrupted status 为 true，至于说被中断的线程怎么处理这个状态，那是那个线程自己的事。如以下代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (!Thread.interrupted()) &#123;</span><br><span class="line">   doWork();</span><br><span class="line">   System.out.println(<span class="string">&quot;我做完一件事了，准备做下一件，如果没有其他线程中断我的话&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<blockquote>
<p>这种代码就是会响应中断的，它会在干活的时候先判断下中断状态，不过，除了 JDK 源码外，其他用中断的场景还是比较少的，毕竟 JDK 源码非常讲究。</p>
</blockquote>
<p>当然，中断除了是线程状态外，还有其他含义，否则也不需要专门搞一个这个概念出来了。</p>
<p>如果线程处于以下三种情况，那么当线程被中断的时候，能自动感知到：</p>
<ol>
<li><p> 来自 Object 类的 wait()、wait(long)、wait(long, int)，<br>来自 Thread 类的 join()、join(long)、join(long, int)、sleep(long)、sleep(long, int)  </p>
<blockquote>
<p>这几个方法的相同之处是，方法上都有: throws InterruptedException</p>
<p>如果线程阻塞在这些方法上（我们知道，这些方法会让当前线程阻塞），这个时候如果其他线程对这个线程进行了中断，那么这个线程会从这些方法中立即返回，抛出 InterruptedException 异常，同时重置中断状态为 false。</p>
</blockquote>
</li>
<li><p> 实现了 InterruptibleChannel 接口的类中的一些 I/O 阻塞操作，如 DatagramChannel 中的 connect 方法和 receive 方法等  </p>
<blockquote>
<p>如果线程阻塞在这里，中断线程会导致这些方法抛出 ClosedByInterruptException 并重置中断状态。</p>
</blockquote>
</li>
<li><p> Selector 中的 select 方法，参考下我写的 NIO 的文章  </p>
<blockquote>
<p>一旦中断，方法立即返回</p>
</blockquote>
</li>
</ol>
<p>对于以上 3 种情况是最特殊的，因为他们能自动感知到中断（这里说自动，当然也是基于底层实现），<strong>并且在做出相应的操作后都会重置中断状态为 false</strong>。</p>
<p>那是不是只有以上 3 种方法能自动感知到中断呢？不是的，如果线程阻塞在 LockSupport.park(Object obj) 方法，也叫挂起，这个时候的中断也会导致线程唤醒，但是唤醒后不会重置中断状态，所以唤醒后去检测中断状态将是 true。</p>
<h3 id="InterruptedException-概述"><a href="#InterruptedException-概述" class="headerlink" title="InterruptedException 概述"></a>InterruptedException 概述</h3><p>它是一个特殊的异常，不是说 JVM 对其有特殊的处理，而是它的使用场景比较特殊。通常，我们可以看到，像 Object 中的 wait() 方法，ReentrantLock 中的 lockInterruptibly() 方法，Thread 中的 sleep() 方法等等，这些方法都带有 <code>throws InterruptedException</code>，我们通常称这些方法为阻塞方法（blocking method）。</p>
<p>阻塞方法一个很明显的特征是，它们需要花费比较长的时间（不是绝对的，只是说明时间不可控），还有它们的方法结束返回往往依赖于外部条件，如 wait 方法依赖于其他线程的 notify，lock 方法依赖于其他线程的 unlock等等。</p>
<p>当我们看到方法上带有 <code>throws InterruptedException</code> 时，我们就要知道，这个方法应该是阻塞方法，我们如果希望它能早点返回的话，我们往往可以通过中断来实现。</p>
<p>除了几个特殊类（如 Object，Thread等）外，感知中断并提前返回是通过轮询中断状态来实现的。我们自己需要写可中断的方法的时候，就是通过在合适的时机（通常在循环的开始处）去判断线程的中断状态，然后做相应的操作（通常是方法直接返回或者抛出异常）。当然，我们也要看到，如果我们一次循环花的时间比较长的话，那么就需要比较长的时间才能<strong>感知</strong>到线程中断了。</p>
<h3 id="处理中断"><a href="#处理中断" class="headerlink" title="处理中断"></a>处理中断</h3><p>一旦中断发生，我们接收到了这个信息，然后怎么去处理中断呢？本小节将简单分析这个问题。</p>
<p>我们经常会这么写代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    Thread.sleep(<span class="number">10000</span>);</span><br><span class="line">&#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">    <span class="comment">// ignore</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// go on</span></span><br></pre></td></tr></table></figure>


<p>当 sleep 结束继续往下执行的时候，我们往往都不知道这块代码是真的 sleep 了 10 秒，还是只休眠了 1 秒就被中断了。这个代码的问题在于，我们将这个异常信息吞掉了。（对于 sleep 方法，我相信大部分情况下，我们都不在意是否是中断了，这里是举例）</p>
<p>AQS 的做法很值得我们借鉴，我们知道 ReentrantLock 有两种 lock 方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    sync.lock();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">lockInterruptibly</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">    sync.acquireInterruptibly(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>前面我们提到过，lock() 方法不响应中断。如果 thread1 调用了 lock() 方法，过了很久还没抢到锁，这个时候 thread2 对其进行了中断，thread1 是不响应这个请求的，它会继续抢锁，当然它不会把“被中断”这个信息扔掉。我们可以看以下代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">acquire</span><span class="params">(<span class="keyword">int</span> arg)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!tryAcquire(arg) &amp;&amp;</span><br><span class="line">        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</span><br><span class="line">        <span class="comment">// 我们看到，这里也没做任何特殊处理，就是记录下来中断状态。</span></span><br><span class="line">        <span class="comment">// 这样，如果外层方法需要去检测的时候，至少我们没有把这个信息丢了</span></span><br><span class="line">        selfInterrupt();<span class="comment">// Thread.currentThread().interrupt();</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>而对于 lockInterruptibly() 方法，因为其方法上面有 <code>throws InterruptedException</code> ，这个信号告诉我们，如果我们要取消线程抢锁，直接中断这个线程即可，它会立即返回，抛出 InterruptedException 异常。</p>
<p>在并发包中，有非常多的这种处理中断的例子，提供两个方法，分别为响应中断和不响应中断，对于不响应中断的方法，记录中断而不是丢失这个信息。如 Condition 中的两个方法就是这样的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">await</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException</span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">awaitUninterruptibly</span><span class="params">()</span></span>;</span><br></pre></td></tr></table></figure>


<blockquote>
<p>通常，如果方法会抛出 InterruptedException 异常，往往方法体的第一句就是：</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">await</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (Thread.interrupted())</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> InterruptedException();</span><br><span class="line"> 	...... </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>熟练使用中断，对于我们写出优雅的代码是有帮助的，也有助于我们分析别人的源码。</p>
</div><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><div class="story post-story"><p>这篇文章的信息量真的很大，如果你花了时间，还是没有看懂，那是我的错了。</p>
<p>欢迎大家向我提问，我不一定能每次都及时出现，我出现也不一定能解决大家的问题，欢迎探讨。</p>
<blockquote>
<p><a target="_blank" rel="noopener" href="https://javadoop.com/post/AbstractQueuedSynchronizer-2">https://javadoop.com/post/AbstractQueuedSynchronizer-2</a></p>
</blockquote>

</div>
  </div>
  
  
    
    <div class='footer'>
      
      
      
        <div class='copyright'>
          <blockquote>
            
              
                <p>博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议</p>

              
            
              
                <p>本文永久链接是：<a href=https://www.bcoder.top/2021/09/05/Javadoop%E7%B3%BB%E5%88%97%EF%BC%9A%E4%B8%80%E8%A1%8C%E4%B8%80%E8%A1%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E6%B8%85%E6%A5%9A-AbstractQueuedSynchronizer-%E4%BA%8C/>https://www.bcoder.top/2021/09/05/Javadoop%E7%B3%BB%E5%88%97%EF%BC%9A%E4%B8%80%E8%A1%8C%E4%B8%80%E8%A1%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E6%B8%85%E6%A5%9A-AbstractQueuedSynchronizer-%E4%BA%8C/</a></p>
              
            
          </blockquote>
        </div>
      
      
    </div>
  
  
    


  <div class='article-meta' id="bottom">
    <div class='new-meta-box'>
      
        
          <div class="new-meta-item date" itemprop="dateModified" datetime="2021-09-05T15:18:05+08:00">
  <a class='notlink' href="/" onclick="return false;" title="Click me!" >
    <i class="fas fa-edit fa-fw" aria-hidden="true"></i>
    <p>更新于：Sep 5, 2021</p>
  </a>
</div>

        
      
        
          
  
  <div class="new-meta-item meta-tags"><a class="tag" href="/tags/AQS/" rel="nofollow"><i class="fas fa-hashtag fa-fw" aria-hidden="true"></i><p>AQS</p></a></div>
  <span hidden itemprop="keywords">AQS</span>


        
      
        
          
  <div class="new-meta-item share -mob-share-list">
  <div class="-mob-share-list share-body">
    
      
        <a class="-mob-share-qq" title="" rel="external nofollow noopener noreferrer noopener"
          
          target="_blank" href="http://connect.qq.com/widget/shareqq/index.html?url=https://www.bcoder.top/2021/09/05/Javadoop%E7%B3%BB%E5%88%97%EF%BC%9A%E4%B8%80%E8%A1%8C%E4%B8%80%E8%A1%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E6%B8%85%E6%A5%9A-AbstractQueuedSynchronizer-%E4%BA%8C/&title=Javadoop系列：一行一行源码分析清楚 AbstractQueuedSynchronizer (二) - 进击的Coder&summary="
          
          >
          
            <img src="https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/logo/128/qq.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/logo/128/qq.png" srcset="" alt="This is a picture without description" >
          
        </a>
      
    
      
        <a class="-mob-share-qzone" title="" rel="external nofollow noopener noreferrer noopener"
          
          target="_blank" href="https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=https://www.bcoder.top/2021/09/05/Javadoop%E7%B3%BB%E5%88%97%EF%BC%9A%E4%B8%80%E8%A1%8C%E4%B8%80%E8%A1%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E6%B8%85%E6%A5%9A-AbstractQueuedSynchronizer-%E4%BA%8C/&title=Javadoop系列：一行一行源码分析清楚 AbstractQueuedSynchronizer (二) - 进击的Coder&summary="
          
          >
          
            <img src="https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/logo/128/qzone.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/logo/128/qzone.png" srcset="" alt="This is a picture without description" >
          
        </a>
      
    
      
        <a class="-mob-share-weibo" title="" rel="external nofollow noopener noreferrer noopener"
          
          target="_blank" href="http://service.weibo.com/share/share.php?url=https://www.bcoder.top/2021/09/05/Javadoop%E7%B3%BB%E5%88%97%EF%BC%9A%E4%B8%80%E8%A1%8C%E4%B8%80%E8%A1%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E6%B8%85%E6%A5%9A-AbstractQueuedSynchronizer-%E4%BA%8C/&title=Javadoop系列：一行一行源码分析清楚 AbstractQueuedSynchronizer (二) - 进击的Coder&summary="
          
          >
          
            <img src="https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/logo/128/weibo.png" class="lazyload" data-srcset="https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/logo/128/weibo.png" srcset="" alt="This is a picture without description" >
          
        </a>
      
    
      
    
      
    
  </div>
</div>



        
      
    </div>
    <!-- Custom Files bottomMeta begin -->
    
    <!-- Custom Files bottomMeta end -->
  </div>


  
  

  
    <div class="prev-next">
      
        <a class='prev' href='/2021/09/05/AbstractQueuedSynchronizer%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90-%E6%B7%B1%E5%88%BB%E8%A7%A3%E6%9E%90%E4%B8%8E%E6%A8%A1%E6%8B%9F%E7%BA%BF%E7%A8%8B%E7%AB%9E%E4%BA%89%E8%B5%84%E6%BA%90/'>
          <p class='title'><i class="fas fa-chevron-left" aria-hidden="true"></i>AbstractQueuedSynchronizer源码剖析- 深刻解析与模拟线程竞争资源</p>
          <p class='content'>1、细说AQS在深入分析AQS之前，我想先从AQS的功能上说明下AQS，站在使用者的角度，AQS的功能可以分为两类：独占锁和共享锁。它的所有子类中，要么实现并使用了它独占锁的API，要么使用了共...</p>
        </a>
      
      
        <a class='next' href='/2021/09/05/Javadoop%E7%B3%BB%E5%88%97%EF%BC%9A%E4%B8%80%E8%A1%8C%E4%B8%80%E8%A1%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E6%B8%85%E6%A5%9AAbstractQueuedSynchronizer/'>
          <p class='title'>Javadoop系列：一行一行源码分析清楚AbstractQueuedSynchronizer<i class="fas fa-chevron-right" aria-hidden="true"></i></p>
          <p class='content'>在分析 Java 并发包 java.util.concurrent 源码的时候，少不了需要了解 AbstractQueuedSynchronizer（以下简写AQS）这个抽象类，因为它是 Jav...</p>
        </a>
      
    </div>
  
  <!-- Custom Files postEnd begin-->
  
  <!-- Custom Files postEnd end-->
</article>


  

  
  <article class="post white-box reveal shadow" id="comments">
    <span hidden>
      <meta itemprop="discussionUrl" content="/2021/09/05/Javadoop%E7%B3%BB%E5%88%97%EF%BC%9A%E4%B8%80%E8%A1%8C%E4%B8%80%E8%A1%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E6%B8%85%E6%A5%9A-AbstractQueuedSynchronizer-%E4%BA%8C/#comments">
    </span>
    <p ct><i class='fas fa-comments'></i> 评论</p>
    
    
    <div id="layoutHelper-comments"></div>

  </article>
  





</div>
<aside id='l_side' itemscope itemtype="http://schema.org/WPSideBar">
  
  
    
    



  <section class="widget toc-wrapper shadow desktop mobile" id="toc-div" >
    
  <header>
    
      <i class="fas fa-list fa-fw" aria-hidden="true"></i><span class='name'>本文目录</span>
    
  </header>


    <div class='content'>
        <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%85%AC%E5%B9%B3%E9%94%81%E5%92%8C%E9%9D%9E%E5%85%AC%E5%B9%B3%E9%94%81"><span class="toc-text">公平锁和非公平锁</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Condition"><span class="toc-text">Condition</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-%E5%B0%86%E8%8A%82%E7%82%B9%E5%8A%A0%E5%85%A5%E5%88%B0%E6%9D%A1%E4%BB%B6%E9%98%9F%E5%88%97"><span class="toc-text">1. 将节点加入到条件队列</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-%E5%AE%8C%E5%85%A8%E9%87%8A%E6%94%BE%E7%8B%AC%E5%8D%A0%E9%94%81"><span class="toc-text">2. 完全释放独占锁</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-%E7%AD%89%E5%BE%85%E8%BF%9B%E5%85%A5%E9%98%BB%E5%A1%9E%E9%98%9F%E5%88%97"><span class="toc-text">3. 等待进入阻塞队列</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-signal-%E5%94%A4%E9%86%92%E7%BA%BF%E7%A8%8B%EF%BC%8C%E8%BD%AC%E7%A7%BB%E5%88%B0%E9%98%BB%E5%A1%9E%E9%98%9F%E5%88%97"><span class="toc-text">4. signal 唤醒线程，转移到阻塞队列</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-%E5%94%A4%E9%86%92%E5%90%8E%E6%A3%80%E6%9F%A5%E4%B8%AD%E6%96%AD%E7%8A%B6%E6%80%81"><span class="toc-text">5. 唤醒后检查中断状态</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-%E8%8E%B7%E5%8F%96%E7%8B%AC%E5%8D%A0%E9%94%81"><span class="toc-text">6. 获取独占锁</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-%E5%A4%84%E7%90%86%E4%B8%AD%E6%96%AD%E7%8A%B6%E6%80%81"><span class="toc-text">7. 处理中断状态</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B8%A6%E8%B6%85%E6%97%B6%E6%9C%BA%E5%88%B6%E7%9A%84-await"><span class="toc-text">* 带超时机制的 await</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%B8%8D%E6%8A%9B%E5%87%BA-InterruptedException-%E7%9A%84-await"><span class="toc-text">* 不抛出 InterruptedException 的 await</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#AbstractQueuedSynchronizer-%E7%8B%AC%E5%8D%A0%E9%94%81%E7%9A%84%E5%8F%96%E6%B6%88%E6%8E%92%E9%98%9F"><span class="toc-text">AbstractQueuedSynchronizer 独占锁的取消排队</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%86%8D%E8%AF%B4-java-%E7%BA%BF%E7%A8%8B%E4%B8%AD%E6%96%AD%E5%92%8C-InterruptedException-%E5%BC%82%E5%B8%B8"><span class="toc-text">再说 java 线程中断和 InterruptedException 异常</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%BA%BF%E7%A8%8B%E4%B8%AD%E6%96%AD"><span class="toc-text">线程中断</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#InterruptedException-%E6%A6%82%E8%BF%B0"><span class="toc-text">InterruptedException 概述</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A4%84%E7%90%86%E4%B8%AD%E6%96%AD"><span class="toc-text">处理中断</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-text">总结</span></a></li></ol>
    </div>
  </section>


  


  <!-- Custom Files side begin -->
  
  <!-- Custom Files side end -->
</aside>



          <!--此文件用来存放一些不方便取值的变量-->
<!--思路大概是将值藏到重加载的区域内-->

<script>
  window.pdata={}
  pdata.ispage=true;
  pdata.postTitle="Javadoop系列：一行一行源码分析清楚 AbstractQueuedSynchronizer (二)";
  pdata.commentPath="";
  pdata.commentPlaceholder="";
  //  see: /layout/_partial/scripts/_ctrl/coverCtrl.ejs
  
    // header
    var l_header=document.getElementById("l_header");
    
    l_header.classList.add("show");
    
    
      // cover
      var cover_wrapper=document.querySelector('#l_cover .cover-wrapper');
      var scroll_down=document.getElementById('scroll-down');
      cover_wrapper.id="none";
      cover_wrapper.style.display="none";
      scroll_down.style.display="none";
    
  
</script>

        </div>
        
  
  <footer class="footer clearfix"  itemscope itemtype="http://schema.org/WPFooter">
    <br><br>
    
      
        <div class="aplayer-container">
          


        </div>
      
    
      
        <br>
        <div class="social-wrapper" itemprop="about" itemscope itemtype="http://schema.org/Thing">
          
            
          
            
          
            
          
        </div>
      
    
      
        <div><p>Blog content follows the <a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en">Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) License</a></p>
</div>
      
    
      
        
          <div><p><span id="lc-sv">本站总访问量为 <span id='number'><i class="fas fa-circle-notch fa-spin fa-fw" aria-hidden="true"></i></span> 次</span> <span id="lc-uv">访客数为 <span id='number'><i class="fas fa-circle-notch fa-spin fa-fw" aria-hidden="true"></i></span> 人</span></p>
</div>
        
      
    
      
        Use
        <a href="https://github.com/volantis-x/hexo-theme-volantis/tree/5.0.0.alpha.2" target="_blank" class="codename">Volantis</a>
        as theme
      
    
      
        <div class='copyright'>
        <p><a href="/">Copyright © 2017-2020 XXX</a></p>

        </div>
      
    
    <!-- Custom Files footer begin-->
    
    <!-- Custom Files footer end-->
  </footer>


        <a id="s-top" class="fas fa-arrow-up fa-fw" href="/" onclick="return false;" title="top"></a>
      </div>
    </div>
    <div>
      <script>
  /************这个文件存放不需要重载的全局变量和全局函数*********/
  window.volantis = {}; // volantis 全局变量
  volantis.dom = {}; // 页面Dom see: /source/js/app.js etc.
  /******************** volantis.EventListener ********************************/
  // 事件监听器 see: /source/js/app.js
  volantis.EventListener = {}
  // 这里存放pjax切换页面时将被移除的事件监听器
  volantis.EventListener.list = []
  //构造方法
  function volantisEventListener(type, f, ele) {
    this.type = type
    this.f = f
    this.ele = ele
  }
  // 移除事件监听器
  volantis.EventListener.remove = () => {
    volantis.EventListener.list.forEach(function (i) {
      i.ele.removeEventListener(i.type, i.f, false)
    })
    volantis.EventListener.list = []
  }
  /******************** volantis.dom.$ ********************************/
  // 注：这里没有选择器，也没有forEach一次只处理一个dom，这里重新封装主题常用的dom方法，返回的是dom对象，对象包含了以下方法，同时保留dom的原生API
  function volantisDom(ele) {
    if (!ele) ele = document.createElement("div")
    this.ele = ele;
    // ==============================================================
    this.ele.find = (c) => {
      let q = this.ele.querySelector(c)
      if (q)
        return new volantisDom(q)
    }
    // ==============================================================
    this.ele.hasClass = (c) => {
      return this.ele.className.match(new RegExp('(\\s|^)' + c + '(\\s|$)'));
    }
    this.ele.addClass = (c) => {
      this.ele.classList.add(c);
      return this.ele
    }
    this.ele.removeClass = (c) => {
      this.ele.classList.remove(c);
      return this.ele
    }
    this.ele.toggleClass = (c) => {
      if (this.ele.hasClass(c)) {
        this.ele.removeClass(c)
      } else {
        this.ele.addClass(c)
      }
      return this.ele
    }
    // ==============================================================
    // 参数 r 为 true 表示pjax切换页面时事件监听器将被移除，false不移除
    this.ele.on = (c, f, r = 1) => {
      this.ele.addEventListener(c, f, false)
      if (r) {
        volantis.EventListener.list.push(new volantisEventListener(c, f, this.ele))
      }
      return this.ele
    }
    this.ele.click = (f, r) => {
      this.ele.on("click", f, r)
      return this.ele
    }
    this.ele.scroll = (f, r) => {
      this.ele.on("scroll", f, r)
      return this.ele
    }
    // ==============================================================
    this.ele.html = (c) => {
      // if(c=== undefined){
      //   return this.ele.innerHTML
      // }else{
      this.ele.innerHTML = c
      return this.ele
      // }
    }
    // ==============================================================
    this.ele.hide = (c) => {
      this.ele.style.display = "none"
      return this.ele
    }
    this.ele.show = (c) => {
      this.ele.style.display = "block"
      return this.ele
    }
    // ==============================================================
    return this.ele
  }
  volantis.dom.$ = (ele) => {
    return !!ele ? new volantisDom(ele) : null;
  }
  /******************** volantis.dom ********************************/
  // 页面选择器 将dom对象缓存起来 see: /source/js/app.js etc.
  volantis.dom.bodyAnchor = volantis.dom.$(document.getElementById("safearea")); // 页面主体
  volantis.dom.topBtn = volantis.dom.$(document.getElementById('s-top')); // 向上
  volantis.dom.wrapper = volantis.dom.$(document.getElementById('wrapper')); // 整个导航栏
  volantis.dom.coverAnchor = volantis.dom.$(document.querySelector('#l_cover .cover-wrapper')); // 1个
  volantis.dom.switcher = volantis.dom.$(document.querySelector('#l_header .switcher .s-search')); // 搜索按钮   移动端 1个
  volantis.dom.header = volantis.dom.$(document.getElementById('l_header')); // 移动端导航栏
  volantis.dom.search = volantis.dom.$(document.querySelector('#l_header .m_search')); // 搜索框 桌面端 移动端 1个
  volantis.dom.mPhoneList = volantis.dom.$(document.querySelectorAll('#l_header .m-phone .list-v')); //  手机端 子菜单 多个
  /******************** RunItem ********************************/
  function RunItem() {
    this.list = []; // 存放回调函数
    this.start = () => {
      for (var i = 0; i < this.list.length; i++) {
        this.list[i].run();
      }
    };
    this.push = (fn, name, setRequestAnimationFrame = true) => {
      let myfn = fn
      if (setRequestAnimationFrame) {
        myfn = ()=>{
          volantis.requestAnimationFrame(fn)
        }
      }
      var f = new Item(myfn, name);
      this.list.push(f);
    };
    // 构造一个可以run的对象
    function Item(fn, name) {
      // 函数名称
      this.name = name || fn.name;
      // run方法
      this.run = () => {
        try {
          fn()
        } catch (error) {
          console.log(error);
        }
      };
    }
  }
  /******************** Pjax ********************************/
  // /layout/_plugins/pjax/index.ejs
  // volantis.pjax.send(callBack[,"callBackName"]) 传入pjax:send回调函数
  // volantis.pjax.push(callBack[,"callBackName"]) 传入pjax:complete回调函数
  // volantis.pjax.error(callBack[,"callBackName"]) 传入pjax:error回调函数
  volantis.pjax = {};
  volantis.pjax.method = {
    complete: new RunItem(),
    error: new RunItem(),
    send: new RunItem(),
  };
  volantis.pjax = {
    ...volantis.pjax,
    push: volantis.pjax.method.complete.push,
    error: volantis.pjax.method.error.push,
    send: volantis.pjax.method.send.push,
  };
  /********************  Dark Mode  ********************************/
  // /layout/_partial/scripts/darkmode.ejs
  // volantis.dark.mode 当前模式 dark or light
  // volantis.dark.toggle() 暗黑模式触发器
  // volantis.dark.push(callBack[,"callBackName"]) 传入触发器回调函数
  volantis.dark = {};
  volantis.dark.method = {
    toggle: new RunItem(),
  };
  volantis.dark = {
    ...volantis.dark,
    push: volantis.dark.method.toggle.push,
  };
  /********************  Message  ********************************/
  // /layout/_plugins/message/script.ejs
  // volantis.message
  /********************  isMobile  ********************************/
  // /source/js/app.js
  // volantis.isMobile
  // volantis.isMobileOld
  /********************脚本动态加载函数********************************/
  // volantis.js(src, cb)  cb 可以传入onload回调函数 或者 JSON对象 例如: volantis.js("src", ()=>{}) 或 volantis.js("src", {defer:true,onload:()=>{}})
  // volantis.css(src)

  // 返回Promise对象，如下方法同步加载资源，这利于处理文件资源之间的依赖关系，例如：APlayer 需要在 MetingJS 之前加载
  // (async () => {
  //     await volantis.js("...theme.plugins.aplayer.js.aplayer...")
  //     await volantis.js("...theme.plugins.aplayer.js.meting...")
  // })();

  // 已经加入了setTimeout
  volantis.js = (src, cb) => {
    return new Promise(resolve => {
      setTimeout(function () {
        var HEAD = document.getElementsByTagName("head")[0] || document.documentElement;
        var script = document.createElement("script");
        script.setAttribute("type", "text/javascript");
        if (cb) {
          if (JSON.stringify(cb)) {
            for (let p in cb) {
              if (p == "onload") {
                script[p] = () => {
                  cb[p]()
                  resolve()
                }
              } else {
                script[p] = cb[p]
                script.onload = resolve
              }
            }
          } else {
            script.onload = () => {
              cb()
              resolve()
            };
          }
        } else {
          script.onload = resolve
        }
        script.setAttribute("src", src);
        HEAD.appendChild(script);
      });
    });
  }
  volantis.css = (src) => {
    return new Promise(resolve => {
      setTimeout(function () {
        var link = document.createElement('link');
        link.rel = "stylesheet";
        link.href = src;
        link.onload = resolve;
        document.getElementsByTagName("head")[0].appendChild(link);
      });
    });
  }
  /********************按需加载的插件********************************/
  // volantis.import.jQuery().then(()=>{})
  volantis.import = {
    jQuery: () => {
      if (typeof jQuery == "undefined") {
        return volantis.js("https://cdn.jsdelivr.net/npm/jquery@3.5/dist/jquery.min.js")
      } else {
        return new Promise(resolve => {
          resolve()
        });
      }
    }
  }
  /********************** requestAnimationFrame ********************************/
  // 1、requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来，在一次重绘或回流中就完成，并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率，一般来说，这个频率为每秒60帧。
  // 2、在隐藏或不可见的元素中，requestAnimationFrame 将不会进行重绘或回流，这当然就意味着更少的的 cpu，gpu 和内存使用量。
  volantis.requestAnimationFrame = (fn)=>{
    if (!window.requestAnimationFrame) {
      window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame;
    }
    window.requestAnimationFrame(fn)
  }
  /******************************************************************************/
  volantis.layoutHelper = (helper, html, opt)=>{
    opt = Object.assign({clean:false, pjax:true}, opt)
    function myhelper(helper, html, clean) {
      volantis.tempDiv = document.createElement("div");
      volantis.tempDiv.innerHTML = html;
      let layoutHelper = document.querySelector("#layoutHelper-"+helper)
      if (layoutHelper) {
        if (clean) {
          layoutHelper.innerHTML = ""
        }
        layoutHelper.append(volantis.tempDiv);
      }
    }
    myhelper(helper, html, opt.clean)
    if (opt.pjax) {
      volantis.pjax.push(()=>{
        myhelper(helper, html, opt.clean)
      },"layoutHelper-"+helper)
    }
  }
  /******************************************************************************/
  /******************************************************************************/
</script>
<script>
  
  volantis.css("https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.14/css/all.min.css");
  
  
  
</script>

<!-- required -->


<!-- internal -->


  <script>
  volantis.message = (title, message, option = {}, done = null) => {
    if (typeof iziToast == "undefined") {
      volantis.css('/css/message.css?time=1630853129176')
      volantis.js('/js/message.js?time=1630853129176', () => {
        tozashMessage(title, message, option, done);
      });
    } else {
      tozashMessage(title, message, option, done);
    }

    function tozashMessage(title, message, option, done) {
      const {
        icon,
        time,
        position,
        transitionIn,
        transitionOut,
        messageColor,
        titleColor,
        backgroundColor,
        zindex
      } = option;
      iziToast.show({
        layout: '2',
        icon: 'Fontawesome',
        closeOnEscape: 'true',
        displayMode: 'replace',
        transitionIn: transitionIn || 'bounceInLeft',
        transitionOut: transitionOut || 'fadeOutRight',
        messageColor: messageColor || 'var(--color-text)',
        titleColor: titleColor || 'var(--color-text)',
        backgroundColor: backgroundColor || 'var(--color-card)',
        zindex: zindex || '2147483647',
        icon: icon || 'fas fa-info-circle light-blue',
        timeout: time || '5000',
        position: position || 'topRight',
        title: title,
        message: message,
        onClosed: () => {
          if(done) done();
        },
      });
    }
  };

  volantis.question = (title, message, option = {}, success = null, cancel = null, done = null) => {
    if (typeof iziToast == "undefined") {
      volantis.css('/css/message.css?time=1630853129176')
      volantis.js('/js/message.js?time=1630853129176', () => {
        tozashQuestion(title, message, option, success, cancel, done);
      });
    } else {
      tozashQuestion(title, message, option, success, cancel, done);
    }

    function tozashQuestion(title, message, option, success, cancel, done) {
      const {
        icon,
        time,
        position,
        transitionIn,
        transitionOut,
        messageColor,
        titleColor,
        backgroundColor,
        zindex
      } = option;
      iziToast.question({ 
        id: 'question',
        icon: 'Fontawesome',
        close: false,
        overlay: true,
        displayMode: 'once',
        position: 'center',
        messageColor: messageColor || 'var(--color-text)',
        titleColor: titleColor || 'var(--color-text)',
        backgroundColor: backgroundColor || 'var(--color-card)',
        zindex: zindex || '2147483647',
        icon: icon || 'fas fa-question-circle light-blue',
        timeout: time || '20000',
        title: title,
        message: message,
        buttons: [
          ['<button><b>是</b></button>', (instance, toast) => {
            instance.hide({ transitionOut: transitionOut || 'fadeOut' }, toast, 'button');
            if(success) success(instance, toast)
          }],
          ['<button><b>否</b></button>', (instance, toast) => {
            instance.hide({ transitionOut: transitionOut || 'fadeOut' }, toast, 'button');
            if(cancel) cancel(instance, toast)
          }]
        ],
        onClosed: (instance, toast, closedBy) => {
          if(done) done(instance, toast, closedBy);
        }
      });
    }
  }

  volantis.messageCopyright = {
    enable: 'true' === 'true' ? true : false,
    title: '知识共享许可协议',
    message: '请遵守 CC BY-NC-SA 4.0 协议。',
    icon: 'far fa-copyright light-blue'
  }

  volantis.messageRightMenu = {
    enable: 'true' === 'true' ? true : false,
  }

  if(volantis.messageCopyright.enable) {
    document.body.oncopy = function(){
      volantis.message(volantis.messageCopyright.title, volantis.messageCopyright.message, {
        icon: volantis.messageCopyright.icon
      });
    }; 
  }
</script>




  
  
  
  <div id="rightmenu-wrapper">
    <ul class="list-v rightmenu" id="rightmenu-content">
      <li class='navigation menuNavigation-Content'>
        <a class='nav icon-only fix-cursor-default' onclick='history.back()'><i class='fa fa-arrow-left fa-fw'></i></a>
        <a class='nav icon-only fix-cursor-default' onclick='history.forward()'><i class='fa fa-arrow-right fa-fw'></i></a>
        <a class='nav icon-only fix-cursor-default' onclick='window.location.reload()'><i class='fa fa-redo fa-fw'></i></a>
        <a class='nav icon-only fix-cursor-default' href='/'><i class='fa fa-home fa-fw'></i></a>
      </li>
      <li class='option menuOption-Content'>
        <span class='vlts-menu opt fix-cursor-default menu-Option' data-fn-type='copyText'>
          
          <i class='fa fa-copy fa-fw'></i> Copy Text
        </span>
        <span class='vlts-menu opt fix-cursor-default menu-Option' data-fn-type='copyPaste'>
          
          <i class='fa fa-paste fa-fw mR12'></i> 粘贴文本
        </span>
        <span class='vlts-menu opt fix-cursor-default menu-Option' data-fn-type='copySelect'>
          
          <i class='fa fa-object-ungroup fa-fw mR12'></i> 全选文本
        </span>
        <span class='vlts-menu opt fix-cursor-default menu-Option' data-fn-type='copyCut'>
          
          <i class='fa fa-cut fa-fw mR12'></i> 剪切文本
        </span>
        <span class='vlts-menu opt fix-cursor-default menu-Option' data-fn-type='openTab'>
          
          <i class='fa fa-external-link-square-alt fa-fw mR12'></i> Open Link in New Tab
        </span>
        <span class='vlts-menu opt fix-cursor-default menu-Option' data-fn-type='copySrc'>
          
          <i class='fa fa-image fa-fw mR12'></i> Copy Image Link
        </span>
        <span class='vlts-menu opt fix-cursor-default menu-Option' data-fn-type='copyImg'>
          
          <i class='fa fa-images fa-fw mR12'></i> 复制图片文件
        </span>
      </li>
      
        
      
        
          <hr class="menuLoad-Content">
        
      
        
          
    <li class="menuLoad-Content">
      <a class='vlts-menu fix-cursor-default' href=https://volantis.js.org/faqs/
        
        
        
        
          id="https:volantisjsorgfaqs"
        
      >
        <i class='fa fa-question fa-fw'></i> 常见问题
      </a>
    </li>
  
        
      
        
          
    <li class="menuLoad-Content">
      <a class='vlts-menu fix-cursor-default' href=https://volantis.js.org/examples/
        
        
        
        
          id="https:volantisjsorgexamples"
        
      >
        <i class='fa fa-rss fa-fw'></i> 示例博客
      </a>
    </li>
  
        
      
        
          
    <li class="menuLoad-Content">
      <a class='vlts-menu fix-cursor-default' href=https://volantis.js.org/contributors/
        
        
        
        
          id="https:volantisjsorgcontributors"
        
      >
        <i class='fa fa-fan fa-spin fa-fw'></i> 加入社区
      </a>
    </li>
  
        
      
        
          <hr class="menuLoad-Content">
        
      
        
          
    <li class="menuLoad-Content">
      <a class='vlts-menu fix-cursor-default' href=https://github.com/volantis-x/volantis-docs/
        
        
        
        
          id="https:githubcomvolantis-xvolantis-docs"
        
      >
        <i class='fa fa-code-branch fa-fw'></i> 本站源码
      </a>
    </li>
  
        
      
        
          
    <li class="menuLoad-Content">
      <a class='vlts-menu fix-cursor-default' href=https://github.com/volantis-x/hexo-theme-volantis/
        
        
        
        
          id="https:githubcomvolantis-xhexo-theme-volantis"
        
      >
        <i class='fa fa-code-branch fa-fw'></i> 主题源码
      </a>
    </li>
  
        
      
        
          <hr class="menuLoad-Content">
        
      
        
          <li class='option menuOption-Content'>
            <span class='vlts-menu opt fix-cursor-default' id='printHtml'>
              <i class='fa fa-print fa-fw '></i> 打印页面
            </span>
          </li>  
        
      
        
          <li class='option menuOption-Content'>
            <span class='vlts-menu opt fix-cursor-default toggle-mode-btn' id='menuDarkBtn'>
              <i class='fa fa-moon fa-fw '></i> 暗黑模式
            </span>
          </li>
        
      
        
          <li class='option menuOption-Content'>
            <span class='vlts-menu opt fix-cursor-default' id='readingModel'>
              <i class='fa fa-book-open fa-fw '></i> 阅读模式
            </span>
          </li>  
        
      
        
      
    </ul>
  </div>
  <script>
    volantis.rightMenu = {};
    volantis.rightMenu.faicon = 'fa' || 'fa';
    
    
      volantis.rightMenu.defaultStyles = true;
    
    
    
    
    
      volantis.APlayerController = {};
    
    volantis.js('/js/rightMenu.js?time=1630853129176')
  </script>



<!-- rightmenu要在darkmode之前（ToggleButton） darkmode要在comments之前（volantis.dark.push）-->

  <script>
const rootElement = document.documentElement;
const darkModeStorageKey = "color-scheme";
const rootElementDarkModeAttributeName = "color-scheme";
const setLS = (k, v) => {
    localStorage.setItem(k, v);
};
const removeLS = (k) => {
    localStorage.removeItem(k);
};
const getLS = (k) => {
    return localStorage.getItem(k);
};
const getModeFromCSSMediaQuery = () => {
  return window.matchMedia("(prefers-color-scheme: dark)").matches
    ? "dark"
    : "light";
};
const resetRootDarkModeAttributeAndLS = () => {
  rootElement.removeAttribute(rootElementDarkModeAttributeName);
  removeLS(darkModeStorageKey);
};
const validColorModeKeys = {
  dark: true,
  light: true,
};
const applyCustomDarkModeSettings = (mode) => {
  const currentSetting = mode || getLS(darkModeStorageKey);
  getCustomDarkMode();
  if (currentSetting === getModeFromCSSMediaQuery()) {
    resetRootDarkModeAttributeAndLS();
  } else if (validColorModeKeys[currentSetting]) {
    rootElement.setAttribute(rootElementDarkModeAttributeName, currentSetting);
  } else {
    resetRootDarkModeAttributeAndLS();
  }
};
const invertDarkModeObj = {
  dark: "light",
  light: "dark",
};
/**
 * get target mode
 */
 const getCustomDarkMode = () => {
  let currentSetting = getLS(darkModeStorageKey);
  if (validColorModeKeys[currentSetting]) {
    currentSetting = invertDarkModeObj[currentSetting];
  } else if (currentSetting === null) {
    currentSetting = invertDarkModeObj[getModeFromCSSMediaQuery()];
  } else {
    return;
  }
  if(currentSetting=="dark"){
    volantis.dark.mode="light";
  }else{
    volantis.dark.mode="dark";
  }
  // console.log(volantis.dark.mode)
};
const toggleCustomDarkMode = () => {
  let currentSetting = getLS(darkModeStorageKey);
  if (validColorModeKeys[currentSetting]) {
    currentSetting = invertDarkModeObj[currentSetting];
  } else if (currentSetting === null) {
    currentSetting = invertDarkModeObj[getModeFromCSSMediaQuery()];
  } else {
    return;
  }
  setLS(darkModeStorageKey, currentSetting);
  return currentSetting;
};
/**
 * 暗黑模式触发器
 */
volantis.dark.toggle=()=>{
  const mode = toggleCustomDarkMode();
  applyCustomDarkModeSettings(mode);
  // 使用 volantis.dark.push 方法传入volantis.dark.toggle回调函数 参见layout/_partial/scripts/global.ejs
  volantis.dark.method.toggle.start();
}
/**
 * bind event for toggle button
 */

function bindToggleButton() {
  var btn= document.querySelectorAll("#wrapper .toggle-mode-btn,#rightmenu-wrapper .toggle-mode-btn")
  btn.forEach(function (e) {
    volantis.dom.$(e).on('click',volantis.dark.toggle);
  })
}
applyCustomDarkModeSettings();
document.addEventListener("DOMContentLoaded", ()=>{
  volantis.requestAnimationFrame(bindToggleButton)
});
volantis.pjax.push(bindToggleButton);
</script>




<script>
  function loadIssuesJS() {
    
      const sites_api = document.getElementById('sites-api');
      if (sites_api != undefined && typeof SitesJS === 'undefined') {
        volantis.js("/js/plugins/sites.js?time=1630853129176")
      }
    
    
      const friends_api = document.getElementById('friends-api');
      if (friends_api != undefined && typeof FriendsJS === 'undefined') {
        volantis.js("/js/plugins/friends.js?time=1630853129176")
      }
    
    
      const contributors_api = document.getElementById('contributors-api');
      if (contributors_api != undefined && typeof ContributorsJS === 'undefined') {
        volantis.js("/js/plugins/contributors.js?time=1630853129176")
      }
    
  };
  loadIssuesJS()
  volantis.pjax.push(()=>{
    loadIssuesJS();
  })

</script>




  <script defer src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@17.1.0/dist/lazyload.min.js"></script>
<script>
  // https://www.npmjs.com/package/vanilla-lazyload
  // Set the options globally
  // to make LazyLoad self-initialize
  window.lazyLoadOptions = {
    elements_selector: ".lazyload",
    threshold: 0
  };
  // Listen to the initialization event
  // and get the instance of LazyLoad
  window.addEventListener(
    "LazyLoad::Initialized",
    function (event) {
      window.lazyLoadInstance = event.detail.instance;
    },
    false
  );
  document.addEventListener('DOMContentLoaded', function () {
    lazyLoadInstance.update();
  });
  document.addEventListener('pjax:complete', function () {
    lazyLoadInstance.update();
  });
</script>




  

<script>
  window.FPConfig = {
	delay: 0,
	ignoreKeywords: ["#"],
	maxRPS: 6,
	hoverDelay: 0
  };
</script>
<script defer src="https://cdn.jsdelivr.net/gh/gijo-varghese/flying-pages@2.1.2/flying-pages.min.js"></script>









  <script>
  volantis.layoutHelper("comments",`<div id="giscus_container"></div>`)

  volantis.giscus = {};

  function check_giscus() {
    if (volantis.dark.mode === "dark") {
      volantis.giscus.Theme = 'https://cdn.jsdelivr.net/gh/volantis-x/cdn-volantis@master/css/giscus/dark.css';
    } else {
      volantis.giscus.Theme = 'https://cdn.jsdelivr.net/gh/volantis-x/cdn-volantis@master/css/giscus/light.css';
    }

    return document.getElementById("giscus_container");
  }

  function pjax_giscus() {
    const HEAD = check_giscus();
    if (!HEAD) return;
    let cfg = {"theme":{"light":"https://cdn.jsdelivr.net/gh/volantis-x/cdn-volantis@master/css/giscus/light.css","dark":"https://cdn.jsdelivr.net/gh/volantis-x/cdn-volantis@master/css/giscus/dark.css"}}
    const script = document.createElement('script');
    script.setAttribute('src', 'https://giscus.app/client.js');
    Object.keys(cfg).forEach(k=>{
      if (k != "theme") {
        script.setAttribute('data-'+k, cfg[k]);
      }
    })
    script.setAttribute('data-theme', volantis.giscus.Theme);
    script.setAttribute('crossorigin', "anonymous");
    HEAD.appendChild(script);
  }

  function dark_giscus() {
    const HEAD = check_giscus();
    if (!HEAD) return;

    const message = {
      setConfig: {
        theme: volantis.giscus.Theme
      }
    };
    const giscusIframe = document.querySelector('iframe.giscus-frame');
    giscusIframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app');
  }
  pjax_giscus();
  volantis.pjax.push(pjax_giscus);
  volantis.dark.push(dark_giscus);
</script>






<script src="/js/app.js?time=1630853129176.js"></script>


<!-- optional -->

  <script>
  const SearchServiceDataPathRoot = ("/" || "/").endsWith("/")
    ? "/" || "/"
    : "//" || "/";
  const SearchServiceDataPath = SearchServiceDataPathRoot + "content.json";

  function loadSearchScript() {
    // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs
    return volantis.js("https://cdn.jsdelivr.net/xxxxxxxx/js/search/hexo.js");
  }

  function loadSearchService() {
    loadSearchScript();
    document.querySelectorAll(".input.u-search-input").forEach((e) => {
      e.removeEventListener("focus", loadSearchService, false);
    });
  }

  // 打开并搜索 字符串 s
  function OpenSearch(s) {
    loadSearchScript().then(() => {
      SearchService.setQueryText(s);
      SearchService.search();
    });
  }

  // 访问含有 ?s=xxx  的链接时打开搜索 // 与搜索引擎 structured data 相关: /scripts/helpers/structured-data/lib/config.js
  if (window.location.search && /^\?s=/g.test(window.location.search)) {
    let queryText = decodeURI(window.location.search)
      .replace(/\ /g, "-")
      .replace(/^\?s=/g, "");
    OpenSearch(queryText);
  }

  // 搜索输入框获取焦点时加载搜索
  document.querySelectorAll(".input.u-search-input").forEach((e) => {
    e.addEventListener("focus", loadSearchService, false);
  });
</script>








  <script>



  function pjax_highlightjs_copyCode(){
    if (!(document.querySelector(".highlight .code pre") ||
      document.querySelector(".article pre code"))) {
      return;
    }
    VolantisApp.copyCode(".highlight .code pre, .article pre code")
  }
  volantis.requestAnimationFrame(pjax_highlightjs_copyCode)
  volantis.pjax.push(pjax_highlightjs_copyCode)

</script>




  <script defer>

  const LCCounter = {
    app_id: 'u9j57bwJod4EDmXWdxrwuqQT-MdYXbMMI',
    app_key: 'jfHtEKVE24j0IVCGHbvuFClp',
    custom_api_server: '',

    // 查询存储的记录
    getRecord(Counter, url, title) {
      return new Promise(function (resolve, reject) {
        Counter('get', '/classes/Counter?where=' + encodeURIComponent(JSON.stringify({url})))
          .then(resp => resp.json())
          .then(({results, code, error}) => {
            if (code === 401) {
              throw error;
            }
            if (results && results.length > 0) {
              var record = results[0];
              resolve(record);
            } else {
              Counter('post', '/classes/Counter', {url, title: title, times: 0})
                .then(resp => resp.json())
                .then((record, error) => {
                  if (error) {
                    throw error;
                  }
                  resolve(record);
                }).catch(error => {
                console.error('Failed to create', error);
                reject(error);
              });
            }
          }).catch((error) => {
          console.error('LeanCloud Counter Error:', error);
          reject(error);
        });
      })
    },

    // 发起自增请求
    increment(Counter, incrArr) {
      return new Promise(function (resolve, reject) {
        Counter('post', '/batch', {
          "requests": incrArr
        }).then((res) => {
          res = res.json();
          if (res.error) {
            throw res.error;
          }
          resolve(res);
        }).catch((error) => {
          console.error('Failed to save visitor count', error);
          reject(error);
        });
      });
    },

    // 构建自增请求体
    buildIncrement(objectId) {
      return {
        "method": "PUT",
        "path": `/1.1/classes/Counter/${ objectId }`,
        "body": {
          "times": {
            '__op': 'Increment',
            'amount': 1
          }
        }
      }
    },

    // 校验是否为有效的 UV
    validUV() {
      var key = 'LeanCloudUVTimestamp';
      var flag = localStorage.getItem(key);
      if (flag) {
        // 距离标记小于 24 小时则不计为 UV
        if (new Date().getTime() - parseInt(flag) <= 86400000) {
          return false;
        }
      }
      localStorage.setItem(key, new Date().getTime().toString());
      return true;
    },

    addCount(Counter) {
      var enableIncr = '' === 'true' && window.location.hostname !== 'localhost';
      enableIncr = true;
      var getterArr = [];
      var incrArr = [];
      // 请求 PV 并自增
      var pvCtn = document.querySelector('#lc-sv');
      if (pvCtn || enableIncr) {
        var pvGetter = this.getRecord(Counter, 'https://www.bcoder.top' + '/#lc-sv', 'Visits').then((record) => {
          incrArr.push(this.buildIncrement(record.objectId))
          var eles = document.querySelectorAll('#lc-sv #number');
          if (eles.length > 0) {
            eles.forEach((el,index,array)=>{
              el.innerText = record.times + 1;
              if (pvCtn) {
                pvCtn.style.display = 'inline';
              }
            })
          }
        });
        getterArr.push(pvGetter);
      }

      // 请求 UV 并自增
      var uvCtn = document.querySelector('#lc-uv');
      if (uvCtn || enableIncr) {
        var uvGetter = this.getRecord(Counter, 'https://www.bcoder.top' + '/#lc-uv', 'Visitors').then((record) => {
          var vuv = this.validUV();
          vuv && incrArr.push(this.buildIncrement(record.objectId))
          var eles = document.querySelectorAll('#lc-uv #number');
          if (eles.length > 0) {
            eles.forEach((el,index,array)=>{
              el.innerText = record.times + (vuv ? 1 : 0);
              if (uvCtn) {
                uvCtn.style.display = 'inline';
              }
            })
          }
        });
        getterArr.push(uvGetter);
      }

      // 请求文章的浏览数，如果是当前页面就自增
      var allPV = document.querySelectorAll('#lc-pv');
      if (allPV.length > 0 || enableIncr) {
        for (i = 0; i < allPV.length; i++) {
          let pv = allPV[i];
          let title = pv.getAttribute('data-title');
          var url = 'https://www.bcoder.top' + pv.getAttribute('data-path');
          if (url) {
            var viewGetter = this.getRecord(Counter, url, title).then((record) => {
              // 是当前页面就自增
              let curPath = window.location.pathname;
              if (curPath.includes('index.html')) {
                curPath = curPath.substring(0, curPath.lastIndexOf('index.html'));
              }
              if (pv.getAttribute('data-path') == curPath) {
                incrArr.push(this.buildIncrement(record.objectId));
              }
              if (pv) {
                var ele = pv.querySelector('#lc-pv #number');
                if (ele) {
                  if (pv.getAttribute('data-path') == curPath) {
                    ele.innerText = (record.times || 0) + 1;
                  } else {
                    ele.innerText = record.times || 0;
                  }
                  pv.style.display = 'inline';
                }
              }
            });
            getterArr.push(viewGetter);
          }
        }
      }

      // 如果启动计数自增，批量发起自增请求
      if (enableIncr) {
        Promise.all(getterArr).then(() => {
          incrArr.length > 0 && this.increment(Counter, incrArr);
        })
      }

    },


    fetchData(api_server) {
      var Counter = (method, url, data) => {
        return fetch(`${ api_server }/1.1${ url }`, {
          method,
          headers: {
            'X-LC-Id': this.app_id,
            'X-LC-Key': this.app_key,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(data)
        });
      };
      this.addCount(Counter);
    },

    refreshCounter() {
      var api_server = this.app_id.slice(-9) !== '-MdYXbMMI' ? this.custom_api_server : `https://${ this.app_id.slice(0, 8).toLowerCase() }.api.lncldglobal.com`;
      if (api_server) {
        this.fetchData(api_server);
      } else {
        fetch('https://app-router.leancloud.cn/2/route?appId=' + this.app_id)
          .then(resp => resp.json())
          .then(({api_server}) => {
            this.fetchData('https://' + api_server);
          });
      }
    }

  };

  LCCounter.refreshCounter();

  document.addEventListener('pjax:complete', function () {
    LCCounter.refreshCounter();
  });
</script>









<!-- pjax 标签必须存在于所有页面 否则 pjax error -->
<pjax>

</pjax>

<script>
  function listennSidebarTOC() {
    const navItems = document.querySelectorAll(".toc li");
    if (!navItems.length) return;
    const sections = [...navItems].map((element) => {
      const link = element.querySelector(".toc-link");
      const target = document.getElementById(
        decodeURI(link.getAttribute("href")).replace("#", "")
      );
      link.addEventListener("click", (event) => {
        event.preventDefault();
        window.scrollTo({
          top: target.getBoundingClientRect().top + document.documentElement.scrollTop + 5,
          
          behavior: "smooth"
          
        });
      });
      return target;
    });

    function activateNavByIndex(target) {
      if (target.classList.contains("active-current")) return;

      document.querySelectorAll(".toc .active").forEach((element) => {
        element.classList.remove("active", "active-current");
      });
      target.classList.add("active", "active-current");
      let parent = target.parentNode;
      while (!parent.matches(".toc")) {
        if (parent.matches("li")) parent.classList.add("active");
        parent = parent.parentNode;
      }
    }

    function findIndex(entries) {
      let index = 0;
      let entry = entries[index];
      if (entry.boundingClientRect.top > 0) {
        index = sections.indexOf(entry.target);
        return index === 0 ? 0 : index - 1;
      }
      for (; index < entries.length; index++) {
        if (entries[index].boundingClientRect.top <= 0) {
          entry = entries[index];
        } else {
          return sections.indexOf(entry.target);
        }
      }
      return sections.indexOf(entry.target);
    }

    function createIntersectionObserver(marginTop) {
      marginTop = Math.floor(marginTop + 10000);
      let intersectionObserver = new IntersectionObserver(
        (entries, observe) => {
          let scrollHeight = document.documentElement.scrollHeight;
          if (scrollHeight > marginTop) {
            observe.disconnect();
            createIntersectionObserver(scrollHeight);
            return;
          }
          let index = findIndex(entries);
          activateNavByIndex(navItems[index]);
        }, {
          rootMargin: marginTop + "px 0px -100% 0px",
          threshold: 0,
        }
      );
      sections.forEach((element) => {
        element && intersectionObserver.observe(element);
      });
    }

    createIntersectionObserver(document.documentElement.scrollHeight);
  }

  document.addEventListener("DOMContentLoaded", ()=>{
    volantis.requestAnimationFrame(listennSidebarTOC)
  });
  document.addEventListener("pjax:success", ()=>{
    volantis.requestAnimationFrame(listennSidebarTOC)
  });
</script>

<script>
  // https://web.dev/content-visibility/
  // https://www.caniuse.com/?search=content-visibility
  // https://infrequently.org/2020/12/content-visibility-scroll-fix/
  // https://infrequently.org/2020/12/resize-resilient-deferred-rendering/

  // 备注 目前已知的问题:
  // 动态修改导致的内容高度变化(例如评论框异步渲染的外部盒子高度变化) 无法提前获知, 进而导致的首次滚动条跳动无法去除 (wontfix) 事实上不使用 content-visibility 也会有跳动, 不过是比使用 content-visibility 跳动提前
  // scrollreveal 插件潜在问题 目前尚不明确

  let eqIsh = (a, b, fuzz = 2) => {
    return Math.abs(a - b) <= fuzz;
  };

  let rectNotEQ = (a, b) => {
    return !eqIsh(a.width, b.width) || !eqIsh(a.height, b.height);
  };

  // Keep a map of elements and the dimensions of
  // their place-holders, re-setting the element's
  // intrinsic size when we get updated measurements
  // from observers.
  let spaced = new WeakMap();

  // Only call this when known cheap, post layout
  let reserveSpace = (el, rect = el.getClientBoundingRect()) => {
    let old = spaced.get(el);
    // Set intrinsic size to prevent jumping on un-painting:
    //    https://drafts.csswg.org/css-sizing-4/#intrinsic-size-override
    if (!old || rectNotEQ(old, rect)) {
      spaced.set(el, rect);
      el.style["contain-intrinsic-size"] = `${rect.width}px ${rect.height}px`;
    }
  };

  let iObs = new IntersectionObserver(
    (entries, o) => {
      entries.forEach((entry) => {
        // We don't care if the element is intersecting or
        // has been laid out as our page structure ensures
        // they'll get the right width.
        reserveSpace(entry.target, entry.boundingClientRect);
      });
    },
    { rootMargin: "500px 0px 500px 0px" }
  );

  let rObs = new ResizeObserver((entries, o) => {
    entries.forEach((entry) => {
      reserveSpace(entry.target, entry.contentRect);
    });
  });

  let resizeResilientDeferredRendering = (Selector) => {
    let articles = document.querySelectorAll(Selector);

    if (articles.length) {
      articles.forEach((el) => {
        iObs.observe(el);
        rObs.observe(el);
      });

      // Workaround for Chrome bug, part 2.
      //
      // Re-enable browser management of rendering for the
      // first article after the first paint. Double-rAF
      // to ensure we get called after a layout.
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          articles[0].style["content-visibility"] = "auto";
        });
      });
    }
  };

  let contentVisibilityScrollFix = () => {
    if (!("content-visibility" in document.documentElement.style)) {
      return;
    }
    resizeResilientDeferredRendering(".post-story");
    resizeResilientDeferredRendering("article");
    resizeResilientDeferredRendering(".post-wrapper");
  };
  contentVisibilityScrollFix();
  volantis.pjax.push(contentVisibilityScrollFix);
</script>



  <script type="application/ld+json">[{"@context":"http://schema.org","@type":"Organization","name":"进击的Coder","url":"https://www.bcoder.top/","logo":{"@type":"ImageObject","url":"https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/blog/favicon/android-chrome-192x192.png","width":192,"height":192}},{"@context":"http://schema.org","@type":"Person","name":"zlnnjit","image":{"@type":"ImageObject","url":"https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/blog/favicon/android-chrome-192x192.png"},"url":"https://www.bcoder.top/","sameAs":["https://github.com/volantis-x"],"description":"文章整理与分享"},{"@context":"http://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"https://www.bcoder.top/","name":"进击的Coder"}},{"@type":"ListItem","position":2,"item":{"@id":"https://www.bcoder.top/categories/JUC/","name":"JUC"}},{"@type":"ListItem","position":3,"item":{"@id":"https://www.bcoder.top/2021/09/05/Javadoop系列：一行一行源码分析清楚-AbstractQueuedSynchronizer-二/","name":"Javadoop系列：一行一行源码分析清楚 AbstractQueuedSynchronizer (二)"}}]},{"@context":"http://schema.org","@type":"BlogPosting","headline":"Javadoop系列：一行一行源码分析清楚 AbstractQueuedSynchronizer (二)","description":"","datePublished":"2021-09-05T07:17:50.000Z","dateModified":"2021-09-05T07:18:05.267Z","inLanguage":"en","mainEntityOfPage":{"@type":"WebPage","@id":"https://www.bcoder.top/2021/09/05/Javadoop%E7%B3%BB%E5%88%97%EF%BC%9A%E4%B8%80%E8%A1%8C%E4%B8%80%E8%A1%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E6%B8%85%E6%A5%9A-AbstractQueuedSynchronizer-%E4%BA%8C/"},"author":{"@type":"Person","name":"zlnnjit","image":{"@type":"ImageObject","url":"https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/blog/favicon/android-chrome-192x192.png"},"url":"https://www.bcoder.top/"},"publisher":{"@type":"Organization","name":"进击的Coder","logo":{"@type":"ImageObject","url":"https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/blog/favicon/android-chrome-192x192.png","width":192,"height":192}},"url":"https://www.bcoder.top/2021/09/05/Javadoop%E7%B3%BB%E5%88%97%EF%BC%9A%E4%B8%80%E8%A1%8C%E4%B8%80%E8%A1%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E6%B8%85%E6%A5%9A-AbstractQueuedSynchronizer-%E4%BA%8C/","wordCount":0,"articleSection":"JUC","keywords":"AQS","image":{"@type":"ImageObject","url":"https://cdn.jsdelivr.net/gh/volantis-x/cdn-org/blog/favicon/android-chrome-192x192.png","width":192,"height":192}}]</script>


<!-- more -->



      
        <!-- 
  pjax重载区域接口：
  1.  <pjax></pjax> 标签 pjax 标签必须存在于所有页面 否则 pjax error
  2.  script[data-pjax]
  3.  .pjax-reload script
-->



<script src="https://cdn.jsdelivr.net/npm/pjax@0.2.8/pjax.min.js"></script>


<script>
    var pjax;
    document.addEventListener('DOMContentLoaded', function () {
      pjax = new Pjax({
        elements: 'a[href]:not([href^="#"]):not([href="javascript:void(0)"]):not([pjax-fancybox]):not([onclick="return false;"])',
        selectors: [
          "title",
          
          "#pjax-container",
          "#pjax-header-nav-list",
          "pjax", // <pjax></pjax> 标签
          "script[data-pjax], .pjax-reload script" // script标签添加data-pjax 或 script标签外层添加.pjax-reload 的script代码段重载
        ],
        cacheBust: false,   // url 地址追加时间戳，用以避免浏览器缓存
        timeout: 5000
      });
    });

    document.addEventListener('pjax:send', function (e) {
      //window.stop(); // 相当于点击了浏览器的停止按钮

      try {
        var currentUrl = window.location.pathname;
        var targetUrl = e.triggerElement.href;
        var banUrl = [""];
        if (banUrl[0] != "") {
          banUrl.forEach(item => {
            if(currentUrl.indexOf(item) != -1 || targetUrl.indexOf(item) != -1) {
              window.location.href = targetUrl;
            }
          });
        }
      } catch (error) {}

      // 使用 volantis.pjax.send 方法传入pjax:send回调函数 参见layout/_partial/scripts/global.ejs
      volantis.pjax.method.send.start();
    });

    document.addEventListener('pjax:complete', function () {
      // 使用 volantis.pjax.push 方法传入重载函数 参见layout/_partial/scripts/global.ejs
      volantis.pjax.method.complete.start();
    });

    document.addEventListener('pjax:error', function (e) {
      // 使用 volantis.pjax.error 方法传入pjax:error回调函数 参见layout/_partial/scripts/global.ejs
      volantis.pjax.method.error.start();
      window.location.href = e.triggerElement.href;
    });
</script>

      
    </div>
  <!-- Custom Files bodyEnd begin-->
  
  <!-- Custom Files bodyEnd end-->
  </body>
</html>
